// META: global=window,worker,shadowrealm
'use strict';
class LipFuzzTransformer {
constructor(substitutions) {
this.substitutions = substitutions;
this.partialChunk = '';
this.lastIndex = undefined;
}
transform(chunk, controller) {
chunk = this.partialChunk + chunk;
this.partialChunk = '';
// lastIndex is the index of the first character after the last substitution.
this.lastIndex = 0;
chunk = chunk.replace(/\{\{([a-zA-Z0-9_-]+)\}\}/g, this.replaceTag.bind(this));
// Regular expression for an incomplete template at the end of a string.
const partialAtEndRegexp = /\{(\{([a-zA-Z0-9_-]+(\})?)?)?$/g;
// Avoid looking at any characters that have already been substituted.
partialAtEndRegexp.lastIndex = this.lastIndex;
this.lastIndex = undefined;
const match = partialAtEndRegexp.exec(chunk);
if (match) {
this.partialChunk = chunk.substring(match.index);
chunk = chunk.substring(0, match.index);
}
controller.enqueue(chunk);
}
flush(controller) {
if (this.partialChunk.length > 0) {
controller.enqueue(this.partialChunk);
}
}
replaceTag(match, p1, offset) {
let replacement = this.substitutions[p1];
if (replacement === undefined) {
replacement = '';
}
this.lastIndex = offset + replacement.length;
return replacement;
}
}
const substitutions = {
in1: 'out1',
in2: 'out2',
quine: '{{quine}}',
bogusPartial: '{{incompleteResult}'
};
const cases = [
{
input: [''],
output: ['']
},
{
input: [],
output: []
},
{
input: ['{{in1}}'],
output: ['out1']
},
{
input: ['z{{in1}}'],
output: ['zout1']
},
{
input: ['{{in1}}q'],
output: ['out1q']
},
{
input: ['{{in1}}{{in1}'],
output: ['out1', '{{in1}']
},
{
input: ['{{in1}}{{in1}', '}'],
output: ['out1', 'out1']
},
{
input: ['{{in1', '}}'],
output: ['', 'out1']
},
{
input: ['{{', 'in1}}'],
output: ['', 'out1']
},
{
input: ['{', '{in1}}'],
output: ['', 'out1']
},
{
input: ['{{', 'in1}'],
output: ['', '', '{{in1}']
},
{
input: ['{'],
output: ['', '{']
},
{
input: ['{', ''],
output: ['', '', '{']
},
{
input: ['{', '{', 'i', 'n', '1', '}', '}'],
output: ['', '', '', '', '', '', 'out1']
},
{
input: ['{{in1}}{{in2}}{{in1}}'],
output: ['out1out2out1']
},
{
input: ['{{wrong}}'],
output: ['']
},
{
input: ['{{wron', 'g}}'],
output: ['', '']
},
{
input: ['{{quine}}'],
output: ['{{quine}}']
},
{
input: ['{{bogusPartial}}'],
output: ['{{incompleteResult}']
},
{
input: ['{{bogusPartial}}}'],
output: ['{{incompleteResult}}']
}
];
for (const testCase of cases) {
const inputChunks = testCase.input;
const outputChunks = testCase.output;
promise_test(() => {
const lft = new TransformStream(new LipFuzzTransformer(substitutions));
const writer = lft.writable.getWriter();
const promises = [];
for (const inputChunk of inputChunks) {
promises.push(writer.write(inputChunk));
}
promises.push(writer.close());
const reader = lft.readable.getReader();
let readerChain = Promise.resolve();
for (const outputChunk of outputChunks) {
readerChain = readerChain.then(() => {
return reader.read().then(({ value, done }) => {
assert_false(done, `done should be false when reading ${outputChunk}`);
assert_equals(value, outputChunk, `value should match outputChunk`);
});
});
}
readerChain = readerChain.then(() => {
return reader.read().then(({ done }) => assert_true(done, `done should be true`));
});
promises.push(readerChain);
return Promise.all(promises);
}, `testing "${inputChunks}" (length ${inputChunks.length})`);
}