1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
const kValidAvailabilities =
['unavailable', 'downloadable', 'downloading', 'available'];
const kAvailableAvailabilities = ['downloadable', 'downloading', 'available'];
const kTestPrompt = 'Please write a sentence in English.';
const kTestContext = 'This is a test; this is only a test.';
const getId = (() => {
let idCount = 0;
return () => idCount++;
})();
// Takes an array of dictionaries mapping keys to value arrays, e.g.:
// [ {Shape: ["Square", "Circle", undefined]}, {Count: [1, 2]} ]
// Returns an array of dictionaries with all value combinations, i.e.:
// [ {Shape: "Square", Count: 1}, {Shape: "Square", Count: 2},
// {Shape: "Circle", Count: 1}, {Shape: "Circle", Count: 2},
// {Shape: undefined, Count: 1}, {Shape: undefined, Count: 2} ]
// Omits dictionary members when the value is undefined; supports array values.
function generateOptionCombinations(optionsSpec) {
// 1. Extract keys from the input specification.
const keys = optionsSpec.map(o => Object.keys(o)[0]);
// 2. Extract the arrays of possible values for each key.
const valueArrays = optionsSpec.map(o => Object.values(o)[0]);
// 3. Compute the Cartesian product of the value arrays using reduce.
const valueCombinations = valueArrays.reduce((accumulator, currentValues) => {
// Init the empty accumulator (first iteration), with single-element
// arrays.
if (accumulator.length === 0) {
return currentValues.map(value => [value]);
}
// Otherwise, expand existing combinations with current values.
return accumulator.flatMap(
existingCombo => currentValues.map(
currentValue => [...existingCombo, currentValue]));
}, []);
// 4. Map each value combination to a result dictionary, skipping
// undefined.
return valueCombinations.map(combination => {
const result = {};
keys.forEach((key, index) => {
if (combination[index] !== undefined) {
result[key] = combination[index];
}
});
return result;
});
}
// The method should take the AbortSignal as an option and return a promise.
async function testAbortPromise(t, method) {
// Test abort signal without custom error.
{
const controller = new AbortController();
const promise = method(controller.signal);
controller.abort();
await promise_rejects_dom(t, 'AbortError', promise);
// Using the same aborted controller will get the `AbortError` as well.
const anotherPromise = method(controller.signal);
await promise_rejects_dom(t, 'AbortError', anotherPromise);
}
// Test abort signal with custom error.
{
const err = new Error('test');
const controller = new AbortController();
const promise = method(controller.signal);
controller.abort(err);
await promise_rejects_exactly(t, err, promise);
// Using the same aborted controller will get the same error as well.
const anotherPromise = method(controller.signal);
await promise_rejects_exactly(t, err, anotherPromise);
}
};
async function testCreateMonitorWithAbortAt(
t, loadedToAbortAt, method, options = {}) {
const {promise: eventPromise, resolve} = Promise.withResolvers();
let hadEvent = false;
function monitor(m) {
m.addEventListener('downloadprogress', e => {
if (e.loaded != loadedToAbortAt) {
return;
}
if (hadEvent) {
assert_unreached(
'This should never be reached since LanguageDetector.create() was aborted.');
return;
}
resolve();
hadEvent = true;
});
}
const controller = new AbortController();
const createPromise =
method({...options, monitor, signal: controller.signal});
await eventPromise;
const err = new Error('test');
controller.abort(err);
await promise_rejects_exactly(t, err, createPromise);
}
async function testCreateMonitorWithAbort(t, method, options = {}) {
await testCreateMonitorWithAbortAt(t, 0, method, options);
await testCreateMonitorWithAbortAt(t, 1, method, options);
}
// The method should take the AbortSignal as an option and return a
// ReadableStream.
async function testAbortReadableStream(t, method) {
// Test abort signal without custom error.
{
const controller = new AbortController();
const stream = method(controller.signal);
controller.abort();
let writableStream = new WritableStream();
await promise_rejects_dom(t, 'AbortError', stream.pipeTo(writableStream));
// Using the same aborted controller will get the `AbortError` as well.
await promise_rejects_dom(t, 'AbortError', new Promise(() => {
method(controller.signal);
}));
}
// Test abort signal with custom error.
{
const error = new DOMException('test', 'VersionError');
const controller = new AbortController();
const stream = method(controller.signal);
controller.abort(error);
let writableStream = new WritableStream();
await promise_rejects_exactly(t, error, stream.pipeTo(writableStream));
// Using the same aborted controller will get the same error.
await promise_rejects_exactly(t, error, new Promise(() => {
method(controller.signal);
}));
}
};
async function testMonitor(createFunc, options = {}) {
let created = false;
const progressEvents = [];
function monitor(m) {
m.addEventListener('downloadprogress', e => {
// No progress events should be fired after `createFunc` resolves.
assert_false(created);
progressEvents.push(e);
});
}
result = await createFunc({...options, monitor});
created = true;
assert_greater_than_equal(progressEvents.length, 2);
assert_equals(progressEvents.at(0).loaded, 0);
assert_equals(progressEvents.at(-1).loaded, 1);
let lastProgressEventLoaded = -1;
for (const progressEvent of progressEvents) {
assert_equals(progressEvent.lengthComputable, true);
assert_equals(progressEvent.total, 1);
assert_less_than_equal(progressEvent.loaded, progressEvent.total);
// `loaded` must be rounded to the nearest 0x10000th.
assert_equals(progressEvent.loaded % (1 / 0x10000), 0);
// Progress events should have monotonically increasing `loaded` values.
assert_greater_than(progressEvent.loaded, lastProgressEventLoaded);
lastProgressEventLoaded = progressEvent.loaded;
}
return result;
}
function run_iframe_test(iframe, test_name) {
const id = getId();
iframe.contentWindow.postMessage({id, type: test_name}, '*');
const {promise, resolve, reject} = Promise.withResolvers();
window.onmessage = message => {
if (message.data.id !== id) {
return;
}
if (message.data.success) {
resolve(message.data.success);
} else {
reject(message.data.err)
}
};
return promise;
}
function load_iframe(src, permission_policy) {
let iframe = document.createElement('iframe');
const {promise, resolve} = Promise.withResolvers();
iframe.onload = () => {
resolve(iframe);
};
iframe.src = src;
iframe.allow = permission_policy;
document.body.appendChild(iframe);
return promise;
}
async function createSummarizer(options = {}) {
await test_driver.bless();
return await Summarizer.create(options);
}
async function createWriter(options = {}) {
await test_driver.bless();
return await Writer.create(options);
}
async function createRewriter(options = {}) {
await test_driver.bless();
return await Rewriter.create(options);
}
|