123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928 |
- import t from 'tap';
- import * as html from '#html';
- import {strictlyThrows} from '#test-lib';
- const {Tag, Attributes, Template} = html;
- t.test(`html.tag`, t => {
- t.plan(14);
- const tag1 =
- html.tag('div',
- {[html.onlyIfContent]: true, foo: 'bar'},
- 'child');
- // 1-5: basic behavior when passing attributes
- t.ok(tag1 instanceof Tag);
- t.ok(tag1.onlyIfContent);
- t.equal(tag1.attributes.get('foo'), 'bar');
- t.equal(tag1.content.length, 1);
- t.equal(tag1.content[0], 'child');
- const tag2 = html.tag('div', ['two', 'children']);
- // 6-8: basic behavior when not passing attributes
- t.equal(tag2.content.length, 2);
- t.equal(tag2.content[0], 'two');
- t.equal(tag2.content[1], 'children');
- const genericTag = html.tag('div');
- const genericTemplate = html.template({
- content: () => html.blank(),
- });
- // 9-10: tag treated as content, not attributes
- const tag3 = html.tag('div', genericTag);
- t.equal(tag3.content.length, 1);
- t.equal(tag3.content[0], genericTag);
- // 11-12: template treated as content, not attributes
- const tag4 = html.tag('div', genericTemplate);
- t.equal(tag4.content.length, 1);
- t.equal(tag4.content[0], genericTemplate);
- // 13-14: deep flattening support
- const tag6 =
- html.tag('div', [
- true &&
- [[[[[[
- true &&
- [[[[[`That's deep.`]]]]],
- ]]]]]],
- ]);
- t.equal(tag6.content.length, 1);
- t.equal(tag6.content[0], `That's deep.`);
- });
- t.test(`Tag (basic interface)`, t => {
- t.plan(11);
- const tag1 = new Tag();
- // 1-5: essential properties & no arguments provided
- t.equal(tag1.tagName, '');
- t.ok(Array.isArray(tag1.content));
- t.equal(tag1.content.length, 0);
- t.ok(tag1.attributes instanceof Attributes);
- t.equal(tag1.attributes.toString(), '');
- const tag2 = new Tag('div', {id: 'banana'}, ['one', 'two', tag1]);
- // 6-11: properties on basic usage
- t.equal(tag2.tagName, 'div');
- t.equal(tag2.content.length, 3);
- t.equal(tag2.content[0], 'one');
- t.equal(tag2.content[1], 'two');
- t.equal(tag2.content[2], tag1);
- t.equal(tag2.attributes.get('id'), 'banana');
- });
- t.test(`Tag (self-closing)`, t => {
- t.plan(10);
- const tag1 = new Tag('br');
- const tag2 = new Tag('div');
- const tag3 = new Tag('div');
- tag3.tagName = 'br';
- // 1-3: selfClosing depends on tagName
- t.ok(tag1.selfClosing);
- t.notOk(tag2.selfClosing);
- t.ok(tag3.selfClosing);
- // 4: constructing self-closing tag with content throws
- t.throws(() => new Tag('br', null, 'bananas'), /self-closing/);
- // 5: setting content on self-closing tag throws
- t.throws(() => { tag1.content = ['suspicious']; }, /self-closing/);
- // 6-9: setting empty content on self-closing tag doesn't throw
- t.doesNotThrow(() => { tag1.content = null; });
- t.doesNotThrow(() => { tag1.content = undefined; });
- t.doesNotThrow(() => { tag1.content = ''; });
- t.doesNotThrow(() => { tag1.content = [null, '', false]; });
- const tag4 = new Tag('div', null, 'bananas');
- // 10: changing tagName to self-closing when tag has content throws
- t.throws(() => { tag4.tagName = 'br'; }, /self-closing/);
- });
- t.test(`Tag (properties from attributes - from constructor)`, t => {
- t.plan(6);
- const tag = new Tag('div', {
- [html.onlyIfContent]: true,
- [html.noEdgeWhitespace]: true,
- [html.joinChildren]: '<br>',
- });
- // 1-3: basic exposed properties from attributes in constructor
- t.ok(tag.onlyIfContent);
- t.ok(tag.noEdgeWhitespace);
- t.equal(tag.joinChildren, '<br>');
- // 4-6: property values stored on attributes with public symbols
- t.equal(tag.attributes.get(html.onlyIfContent), true);
- t.equal(tag.attributes.get(html.noEdgeWhitespace), true);
- t.equal(tag.attributes.get(html.joinChildren), '<br>');
- });
- t.test(`Tag (properties from attributes - mutating)`, t => {
- t.plan(12);
- // 1-3: exposed properties reflect reasonable attribute values
- const tag1 = new Tag('div', {
- [html.onlyIfContent]: true,
- [html.noEdgeWhitespace]: true,
- [html.joinChildren]: '<br>',
- });
- tag1.attributes.set(html.onlyIfContent, false);
- tag1.attributes.remove(html.noEdgeWhitespace);
- tag1.attributes.set(html.joinChildren, '🍇');
- t.equal(tag1.onlyIfContent, false);
- t.equal(tag1.noEdgeWhitespace, false);
- t.equal(tag1.joinChildren, '🍇');
- // 4-6: exposed properties reflect unreasonable attribute values
- const tag2 = new Tag('div', {
- [html.onlyIfContent]: true,
- [html.noEdgeWhitespace]: true,
- [html.joinChildren]: '<br>',
- });
- tag2.attributes.set(html.onlyIfContent, '');
- tag2.attributes.set(html.noEdgeWhitespace, 12345);
- tag2.attributes.set(html.joinChildren, 0.0001);
- t.equal(tag2.onlyIfContent, false);
- t.equal(tag2.noEdgeWhitespace, true);
- t.equal(tag2.joinChildren, '0.0001');
- // 7-9: attribute values reflect reasonable mutated properties
- const tag3 = new Tag('div', null, {
- [html.onlyIfContent]: false,
- [html.noEdgeWhitespace]: true,
- [html.joinChildren]: '🍜',
- })
- tag3.onlyIfContent = true;
- tag3.noEdgeWhitespace = false;
- tag3.joinChildren = '🦑';
- t.equal(tag3.attributes.get(html.onlyIfContent), true);
- t.equal(tag3.attributes.get(html.noEdgeWhitespace), undefined);
- t.equal(tag3.joinChildren, '🦑');
- // 10-12: attribute values reflect unreasonable mutated properties
- const tag4 = new Tag('div', null, {
- [html.onlyIfContent]: false,
- [html.noEdgeWhitespace]: true,
- [html.joinChildren]: '🍜',
- });
- tag4.onlyIfContent = 'armadillo';
- tag4.noEdgeWhitespace = 0;
- tag4.joinChildren = Infinity;
- t.equal(tag4.attributes.get(html.onlyIfContent), true);
- t.equal(tag4.attributes.get(html.noEdgeWhitespace), undefined);
- t.equal(tag4.attributes.get(html.joinChildren), 'Infinity');
- });
- t.test(`Tag.toString`, t => {
- t.plan(9);
- // 1: basic behavior
- const tag1 =
- html.tag('div', 'Content');
- t.equal(tag1.toString(),
- `<div>Content</div>`);
- // 2: stringifies nested element
- const tag2 =
- html.tag('div', html.tag('p', 'Content'));
- t.equal(tag2.toString(),
- `<div><p>Content</p></div>`);
- // 3: stringifies attributes
- const tag3 =
- html.tag('div',
- {
- id: 'banana',
- class: ['foo', 'bar'],
- contenteditable: true,
- biggerthanabreadbox: false,
- saying: `"To light a candle is to cast a shadow..."`,
- tabindex: 413,
- },
- 'Content');
- t.equal(tag3.toString(),
- `<div id="banana" class="foo bar" contenteditable ` +
- `saying=""To light a candle is to cast a shadow..."" ` +
- `tabindex="413">Content</div>`);
- // 4: attributes match input order
- const tag4 =
- html.tag('div',
- {class: ['foo', 'bar'], id: 'banana'},
- 'Content');
- t.equal(tag4.toString(),
- `<div class="foo bar" id="banana">Content</div>`);
- // 5: multiline contented indented
- const tag5 =
- html.tag('div', 'foo\nbar');
- t.equal(tag5.toString(),
- `<div>\n` +
- ` foo\n` +
- ` bar\n` +
- `</div>`);
- // 6: nested multiline content double-indented
- const tag6 =
- html.tag('div', [
- html.tag('p',
- 'foo\nbar'),
- html.tag('span', `I'm on one line!`),
- ]);
- t.equal(tag6.toString(),
- `<div>\n` +
- ` <p>\n` +
- ` foo\n` +
- ` bar\n` +
- ` </p>\n` +
- ` <span>I'm on one line!</span>\n` +
- `</div>`);
- // 7: self-closing (with attributes)
- const tag7 =
- html.tag('article', [
- html.tag('h1', `Title`),
- html.tag('hr', {style: `color: magenta`}),
- html.tag('p', `Shenanigans!`),
- ]);
- t.equal(tag7.toString(),
- `<article>\n` +
- ` <h1>Title</h1>\n` +
- ` <hr style="color: magenta">\n` +
- ` <p>Shenanigans!</p>\n` +
- `</article>`);
- // 8-9: empty tagName passes content through directly
- const tag8 =
- html.tag(null, [
- html.tag('h1', `Foo`),
- html.tag(`h2`, `Bar`),
- ]);
- t.equal(tag8.toString(),
- `<h1>Foo</h1>\n` +
- `<h2>Bar</h2>`);
- const tag9 =
- html.tag(null, {
- [html.joinChildren]: html.tag('br'),
- }, [
- `Say it with me...`,
- `Supercalifragilisticexpialidocious!`
- ]);
- t.equal(tag9.toString(),
- `Say it with me...\n` +
- `<br>\n` +
- `Supercalifragilisticexpialidocious!`);
- });
- t.test(`Tag.toString (onlyIfContent)`, t => {
- t.plan(4);
- // 1-2: basic behavior
- const tag1 =
- html.tag('div',
- {[html.onlyIfContent]: true},
- `Hello!`);
- t.equal(tag1.toString(),
- `<div>Hello!</div>`);
- const tag2 =
- html.tag('div',
- {[html.onlyIfContent]: true},
- '');
- t.equal(tag2.toString(),
- '');
- // 3-4: nested onlyIfContent with "more" content
- const tag3 =
- html.tag('div',
- {[html.onlyIfContent]: true},
- [
- '',
- 0,
- html.tag('h1',
- {[html.onlyIfContent]: true},
- html.tag('strong',
- {[html.onlyIfContent]: true})),
- null,
- false,
- ]);
- t.equal(tag3.toString(),
- '');
- const tag4 =
- html.tag('div',
- {[html.onlyIfContent]: true},
- [
- '',
- 0,
- html.tag('h1',
- {[html.onlyIfContent]: true},
- html.tag('strong')),
- null,
- false,
- ]);
- t.equal(tag4.toString(),
- `<div><h1><strong></strong></h1></div>`);
- });
- t.test(`Tag.toString (joinChildren, noEdgeWhitespace)`, t => {
- t.plan(6);
- // 1: joinChildren: default (\n), noEdgeWhitespace: true
- const tag1 =
- html.tag('div',
- {[html.noEdgeWhitespace]: true},
- [
- 'Foo',
- 'Bar',
- 'Baz',
- ]);
- t.equal(tag1.toString(),
- `<div>Foo\n` +
- ` Bar\n` +
- ` Baz</div>`);
- // 2: joinChildren: one-line string, noEdgeWhitespace: default (false)
- const tag2 =
- html.tag('div',
- {
- [html.joinChildren]:
- html.tag('br', {location: '🍍'}),
- },
- [
- 'Foo',
- 'Bar',
- 'Baz',
- ]);
- t.equal(tag2.toString(),
- `<div>\n` +
- ` Foo\n` +
- ` <br location="🍍">\n` +
- ` Bar\n` +
- ` <br location="🍍">\n` +
- ` Baz\n` +
- `</div>`);
- // 3-4: joinChildren: blank string, noEdgeWhitespace: default (false)
- const tag3 =
- html.tag('div',
- {[html.joinChildren]: ''},
- [
- 'Foo',
- 'Bar',
- 'Baz',
- ]);
- t.equal(tag3.toString(),
- `<div>FooBarBaz</div>`);
- const tag4 =
- html.tag('div',
- {[html.joinChildren]: ''},
- [
- `Ain't I\na cute one?`,
- `~`
- ]);
- t.equal(tag4.toString(),
- `<div>\n` +
- ` Ain't I\n` +
- ` a cute one?~\n` +
- `</div>`);
- // 5: joinChildren: one-line string, noEdgeWhitespace: true
- const tag5 =
- html.tag('div',
- {
- [html.joinChildren]: html.tag('br'),
- [html.noEdgeWhitespace]: true,
- },
- [
- 'Foo',
- 'Bar',
- 'Baz',
- ]);
- t.equal(tag5.toString(),
- `<div>Foo\n` +
- ` <br>\n` +
- ` Bar\n` +
- ` <br>\n` +
- ` Baz</div>`);
- // 6: joinChildren: empty string, noEdgeWhitespace: true
- const tag6 =
- html.tag('span',
- {
- [html.joinChildren]: '',
- [html.noEdgeWhitespace]: true,
- },
- [
- html.tag('i', `Oh yes~ `),
- `You're a cute one`,
- html.tag('sup', `💕`),
- ]);
- t.equal(tag6.toString(),
- `<span><i>Oh yes~ </i>You're a cute one<sup>💕</sup></span>`);
- });
- t.test(`html.template`, t => {
- t.plan(11);
- let contentCalls;
- // 1-4: basic behavior - no slots
- contentCalls = 0;
- const template1 = html.template({
- content() {
- contentCalls++;
- return html.tag('hr');
- },
- });
- t.equal(contentCalls, 0);
- t.equal(template1.toString(), `<hr>`);
- t.equal(contentCalls, 1);
- template1.toString();
- t.equal(contentCalls, 2);
- // 5-10: basic behavior - slots
- contentCalls = 0;
- const template2 = html.template({
- slots: {
- foo: {
- type: 'string',
- default: 'Default Message',
- },
- },
- content(slots) {
- contentCalls++;
- return html.tag('sub', slots.foo.toLowerCase());
- },
- });
- t.equal(contentCalls, 0);
- t.equal(template2.toString(), `<sub>default message</sub>`);
- t.equal(contentCalls, 1);
- template2.setSlot('foo', `R-r-really, me?`);
- t.equal(contentCalls, 1);
- t.equal(template2.toString(), `<sub>r-r-really, me?</sub>`);
- t.equal(contentCalls, 2);
- // 11: slot uses default only for null, not falsey
- const template3 = html.template({
- slots: {
- slot1: {type: 'number', default: 123},
- slot2: {type: 'number', default: 456},
- slot3: {type: 'boolean', default: true},
- slot4: {type: 'string', default: 'banana'},
- },
- content(slots) {
- return html.tag('span', [
- slots.slot1,
- slots.slot2,
- slots.slot3,
- `(length: ${slots.slot4.length})`,
- ].join(' '));
- },
- });
- template3.setSlots({
- slot1: null,
- slot2: 0,
- slot3: false,
- slot4: '',
- });
- t.equal(template3.toString(), `<span>123 0 false (length: 0)</span>`);
- });
- t.test(`Template - description errors`, t => {
- t.plan(14);
- // 1-3: top-level description is object
- strictlyThrows(t,
- () => Template.validateDescription('snooping as usual'),
- new TypeError(`Expected object, got string`));
- strictlyThrows(t,
- () => Template.validateDescription(),
- new TypeError(`Expected object, got undefined`));
- strictlyThrows(t,
- () => Template.validateDescription(null),
- new TypeError(`Expected object, got null`));
- // 4-5: description.content is function
- strictlyThrows(t,
- () => Template.validateDescription({}),
- new AggregateError([
- new TypeError(`Expected description.content`),
- ], `Errors validating template description`));
- strictlyThrows(t,
- () => Template.validateDescription({
- content: 'pingas',
- }),
- new AggregateError([
- new TypeError(`Expected description.content to be function`),
- ], `Errors validating template description`));
- // 6: aggregate error includes template annotation
- strictlyThrows(t,
- () => Template.validateDescription({
- annotation: `my cool template`,
- content: 'pingas',
- }),
- new AggregateError([
- new TypeError(`Expected description.content to be function`),
- ], `Errors validating template "my cool template" description`));
- // 7: description.slots is object
- strictlyThrows(t,
- () => Template.validateDescription({
- slots: 'pingas',
- content: () => {},
- }),
- new AggregateError([
- new TypeError(`Expected description.slots to be object`),
- ], `Errors validating template description`));
- // 8: slot description is object
- strictlyThrows(t,
- () => Template.validateDescription({
- slots: {
- mySlot: 'pingas',
- },
- content: () => {},
- }),
- new AggregateError([
- new AggregateError([
- new TypeError(`(mySlot) Expected slot description to be object`),
- ], `Errors in slot descriptions`),
- ], `Errors validating template description`))
- // 9-10: slot description has validate or default, not both
- strictlyThrows(t,
- () => Template.validateDescription({
- slots: {
- mySlot: {},
- },
- content: () => {},
- }),
- new AggregateError([
- new AggregateError([
- new TypeError(`(mySlot) Expected either slot validate or type`),
- ], `Errors in slot descriptions`),
- ], `Errors validating template description`));
- strictlyThrows(t,
- () => Template.validateDescription({
- slots: {
- mySlot: {
- validate: 'pingas',
- type: 'pingas',
- },
- },
- content: () => {},
- }),
- new AggregateError([
- new AggregateError([
- new TypeError(`(mySlot) Don't specify both slot validate and type`),
- ], `Errors in slot descriptions`),
- ], `Errors validating template description`));
- // 11: slot validate is function
- strictlyThrows(t,
- () => Template.validateDescription({
- slots: {
- mySlot: {
- validate: 'pingas',
- },
- },
- content: () => {},
- }),
- new AggregateError([
- new AggregateError([
- new TypeError(`(mySlot) Expected slot validate to be function`),
- ], `Errors in slot descriptions`),
- ], `Errors validating template description`));
- // 12: slot type is name of built-in type
- strictlyThrows(t,
- () => Template.validateDescription({
- slots: {
- mySlot: {
- type: 'pingas',
- },
- },
- content: () => {},
- }),
- new AggregateError([
- new AggregateError([
- /\(mySlot\) Expected slot type to be one of/,
- ], `Errors in slot descriptions`),
- ], `Errors validating template description`));
- // 13: slot type has specific errors for function & object
- strictlyThrows(t,
- () => Template.validateDescription({
- slots: {
- slot1: {type: 'function'},
- slot2: {type: 'object'},
- },
- content: () => {},
- }),
- new AggregateError([
- new AggregateError([
- new TypeError(`(slot1) Functions shouldn't be provided to slots`),
- new TypeError(`(slot2) Provide validate function instead of type: object`),
- ], `Errors in slot descriptions`),
- ], `Errors validating template description`));
- // 14: all intended types are supported
- t.doesNotThrow(
- () => Template.validateDescription({
- slots: {
- slot1: {type: 'string'},
- slot2: {type: 'number'},
- slot3: {type: 'bigint'},
- slot4: {type: 'boolean'},
- slot5: {type: 'symbol'},
- slot6: {type: 'html', mutable: false},
- },
- content: () => {},
- }));
- });
- t.test(`Template - slot value errors`, t => {
- t.plan(8);
- const template1 = html.template({
- slots: {
- basicString: {type: 'string'},
- basicNumber: {type: 'number'},
- basicBigint: {type: 'bigint'},
- basicBoolean: {type: 'boolean'},
- basicSymbol: {type: 'symbol'},
- basicHTML: {type: 'html', mutable: false},
- },
- content: slots =>
- html.tag('p', [
- `string: ${slots.basicString}`,
- `number: ${slots.basicNumber}`,
- `bigint: ${slots.basicBigint}`,
- `boolean: ${slots.basicBoolean}`,
- `symbol: ${slots.basicSymbol?.toString() ?? 'no symbol'}`,
- `html:`,
- slots.basicHTML,
- ]),
- });
- // 1-2: basic values match type, no error & reflected in content
- t.doesNotThrow(
- () => template1.setSlots({
- basicString: 'pingas',
- basicNumber: 123,
- basicBigint: 1234567891234567n,
- basicBoolean: true,
- basicSymbol: Symbol(`sup`),
- basicHTML: html.tag('span', `SnooPING AS usual, I see!`),
- }));
- t.equal(
- template1.toString(),
- html.tag('p', [
- `string: pingas`,
- `number: 123`,
- `bigint: 1234567891234567`,
- `boolean: true`,
- `symbol: Symbol(sup)`,
- `html:`,
- html.tag('span', `SnooPING AS usual, I see!`),
- ]).toString());
- // 3-4: null matches any type, no error & reflected in content
- t.doesNotThrow(
- () => template1.setSlots({
- basicString: null,
- basicNumber: null,
- basicBigint: null,
- basicBoolean: null,
- basicSymbol: null,
- basicHTML: null,
- }));
- t.equal(
- template1.toString(),
- html.tag('p', [
- `string: null`,
- `number: null`,
- `bigint: null`,
- `boolean: null`,
- `symbol: no symbol`,
- `html:`,
- ]).toString());
- // 5-6: type mismatch throws error, invalidates entire setSlots call
- template1.setSlots({
- basicString: 'pingas',
- basicNumber: 123,
- });
- strictlyThrows(t,
- () => template1.setSlots({
- basicBoolean: false,
- basicSymbol: `I'm not a symbol!`,
- }),
- new AggregateError([
- new TypeError(`(basicSymbol) Slot expects symbol, got string`),
- ], `Error validating template slots`))
- t.equal(
- template1.toString(),
- html.tag('p', [
- `string: pingas`,
- `number: 123`,
- `bigint: null`,
- `boolean: null`,
- `symbol: no symbol`,
- `html:`,
- ]).toString());
- const template2 = html.template({
- slots: {
- strictArrayOfStrings: {
- validate: v => v.strictArrayOf(v.isString),
- default: `Array Of Strings Fallback`.split(' '),
- },
- sparseArrayOfStrings: {
- validate: v => v.sparseArrayOf(v.isString),
- default: ['sparse', null, false, 'strings'],
- },
- arrayOfHTML: {
- validate: v => v.strictArrayOf(v.isHTML),
- default: [],
- },
- },
- content: slots =>
- html.tag('p', [
- html.tag('strong', slots.strictArrayOfStrings),
- `sparseArrayOfStrings length: ${slots.sparseArrayOfStrings.length}`,
- `arrayOfHTML length: ${slots.arrayOfHTML.length}`,
- ]),
- });
- // 7: isHTML behaves as it should, validate fails with validate throw
- strictlyThrows(t,
- () => template2.setSlots({
- strictArrayOfStrings: ['you got it', 'pingas', 0xdeadbeef],
- sparseArrayOfStrings: ['you got it', null, false, 'pingas'],
- arrayOfHTML: [
- html.tag('span'),
- html.template({content: () => 'dog'}),
- html.blank(),
- false && 'dogs',
- null,
- undefined,
- html.tags([
- html.tag('span', 'usual'),
- html.tag('span', 'i'),
- ]),
- ],
- }),
- new AggregateError([
- {
- name: 'AggregateError',
- message: /^\(strictArrayOfStrings\)/,
- errors: {length: 1},
- },
- ], `Error validating template slots`));
- // 8: default slot values respected
- t.equal(
- template2.toString(),
- html.tag('p', [
- html.tag('strong', [
- `Array`,
- `Of`,
- `Strings`,
- `Fallback`,
- ]),
- `sparseArrayOfStrings length: 4`,
- `arrayOfHTML length: 0`,
- ]).toString());
- });
- t.test(`Stationery`, t => {
- t.plan(3);
- // 1-3: basic behavior
- const stationery1 = new html.Stationery({
- slots: {
- slot1: {type: 'string', default: 'apricot'},
- slot2: {type: 'string', default: 'disaster'},
- },
- content: ({slot1, slot2}) => html.tag('span', `${slot1} ${slot2}`),
- });
- const template1 = stationery1.template();
- const template2 = stationery1.template();
- template2.setSlots({slot1: 'aquaduct', slot2: 'dichotomy'});
- const template3 = stationery1.template();
- template3.setSlots({slot2: 'vinaigrette'});
- t.equal(template1.toString(), `<span>apricot disaster</span>`);
- t.equal(template2.toString(), `<span>aquaduct dichotomy</span>`);
- t.equal(template3.toString(), `<span>apricot vinaigrette</span>`);
- });
|