'use strict'; if (self.importScripts) { self.importScripts('/resources/testharness.js'); } // The purpose of this file is to test for objects, attributes and arguments that should not exist. // The test cases are generated from data tables to reduce duplication. // Courtesy of André Bargull. Source is https://esdiscuss.org/topic/isconstructor#content-11. function IsConstructor(o) { try { new new Proxy(o, { construct: () => ({}) })(); return true; } catch (e) { return false; } } for (const func of ['WritableStreamDefaultController', 'WritableStreamDefaultWriter']) { test(() => { assert_equals(self[func], undefined, `${func} should not be defined`); }, `${func} should not be exported on the global object`); } // Now get hold of the symbols so we can test their properties. self.WritableStreamDefaultController = (() => { let controller; new WritableStream({ start(c) { controller = c; } }); return controller.constructor; })(); self.WritableStreamDefaultWriter = new WritableStream().getWriter().constructor; const expected = { WritableStream: { constructor: { type: 'constructor', length: 0 }, locked: { type: 'getter' }, abort: { type: 'method', length: 1 }, getWriter: { type: 'method', length: 0 } }, WritableStreamDefaultController: { constructor: { type: 'constructor', length: 4 }, error: { type: 'method', length: 1 } }, WritableStreamDefaultWriter: { constructor: { type: 'constructor', length: 1 }, closed: { type: 'getter' }, desiredSize: { type: 'getter' }, ready: { type: 'getter' }, abort: { type: 'method', length: 1 }, close: { type: 'method', length: 0 }, releaseLock: { type: 'method', length: 0 }, write: { type: 'method', length: 1 } } }; for (const c in expected) { const properties = expected[c]; const prototype = self[c].prototype; for (const name in properties) { const fullName = `${c}.prototype.${name}`; const descriptor = Object.getOwnPropertyDescriptor(prototype, name); test(() => { const { configurable, enumerable } = descriptor; assert_true(configurable, `${name} should be configurable`); assert_false(enumerable, `${name} should not be enumerable`); }, `${fullName} should have standard properties`); const type = properties[name].type; switch (type) { case 'getter': test(() => { const { writable, get, set } = descriptor; assert_equals(writable, undefined, `${name} should not be a data descriptor`); assert_equals(typeof get, 'function', `${name} should have a getter`); assert_equals(set, undefined, `${name} should not have a setter`); }, `${fullName} should be a getter`); break; case 'constructor': case 'method': test(() => { assert_true(descriptor.writable, `${name} should be writable`); assert_equals(typeof prototype[name], 'function', `${name} should be a function`); assert_equals(prototype[name].length, properties[name].length, `${name} should take ${properties[name].length} arguments`); if (type === 'constructor') { assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`); } else { assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`); } }, `${fullName} should be a ${type}`); break; } } test(() => { const expectedPropertyNames = Object.keys(properties).sort(); const actualPropertyNames = Object.getOwnPropertyNames(prototype).sort(); assert_array_equals(actualPropertyNames, expectedPropertyNames, `${c} properties should match expected properties`); }, `${c}.prototype should have exactly the expected properties`); } const sinkMethods = { start: { length: 1, trigger: () => {} }, write: { length: 2, trigger: writer => writer.write() }, close: { length: 0, trigger: writer => writer.close() }, abort: { length: 1, trigger: writer => writer.abort() } }; for (const method in sinkMethods) { const { length, trigger } = sinkMethods[method]; // Some semantic tests of how sink methods are called can be found in general.js, as well as in the test files // specific to each method. promise_test(() => { let argCount; const ws = new WritableStream({ [method](...args) { argCount = args.length; } }); return Promise.resolve(trigger(ws.getWriter())).then(() => { assert_equals(argCount, length, `${method} should be called with ${length} arguments`); }); }, `sink method ${method} should be called with the right number of arguments`); promise_test(() => { let methodWasCalled = false; function Sink() {} Sink.prototype = { [method]() { methodWasCalled = true; } }; const ws = new WritableStream(new Sink()); return Promise.resolve(trigger(ws.getWriter())).then(() => { assert_true(methodWasCalled, `${method} should be called`); }); }, `sink method ${method} should be called even when it's located on the prototype chain`); if (method !== 'start') { promise_test(t => { const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions', 'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys', 'apply', 'construct']; const handler = { get: t.step_func((target, property) => { if (property === 'type') { return undefined; } assert_in_array(property, ['start', method], `only start() and ${method}() should be called`); return () => Promise.resolve(); }) }; for (const trap of unreachedTraps) { handler[trap] = t.unreached_func(`${trap} should not be trapped`); } const sink = new Proxy({}, handler); const ws = new WritableStream(sink); return trigger(ws.getWriter()); }, `unexpected properties should not be accessed when calling sink method ${method}`); } } done();