tungsten 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/passwordHashing.sxyt.js +2 -2
- package/dist/componentProxyHandlers.d.ts +2 -2
- package/dist/componentProxyHandlers.d.ts.map +1 -1
- package/dist/componentProxyHandlers.js +70 -44
- package/dist/componentProxyHandlers.sxyt.d.ts +2 -0
- package/dist/componentProxyHandlers.sxyt.d.ts.map +1 -0
- package/dist/componentProxyHandlers.sxyt.js +130 -0
- package/package.json +4 -3
- package/dist/testing/createTestApp.sxyt.d.ts +0 -2
- package/dist/testing/createTestApp.sxyt.d.ts.map +0 -1
- package/dist/testing/createTestApp.sxyt.js +0 -424
- package/dist/testing/ensureTestEnvLoaded.d.ts +0 -2
- package/dist/testing/ensureTestEnvLoaded.d.ts.map +0 -1
- package/dist/testing/ensureTestEnvLoaded.js +0 -19
- package/dist/testing/index.d.ts +0 -36
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js +0 -156
|
@@ -7,7 +7,7 @@ describe('hashPassword()', ({
|
|
|
7
7
|
}) => {
|
|
8
8
|
it('hashes a password and returns a string', async () => {
|
|
9
9
|
const hashed = await hashPassword('abcd');
|
|
10
|
-
console.log('hashed: ' + hashed)
|
|
10
|
+
//console.log('hashed: ' + hashed)
|
|
11
11
|
expect(hashed).to.be.a('string');
|
|
12
12
|
expect(hashed.length).to.be.at.least(10);
|
|
13
13
|
});
|
|
@@ -19,7 +19,7 @@ describe('hashPasswordBytes()', ({
|
|
|
19
19
|
const hashedBytes = await hashPasswordBytes('abcd');
|
|
20
20
|
expect(hashedBytes).to.be.an.instanceOf(Buffer);
|
|
21
21
|
const hashedString = hashedBytes.toString();
|
|
22
|
-
console.log('hashedString: ' + hashedString)
|
|
22
|
+
//console.log('hashedString: ' + hashedString)
|
|
23
23
|
expect(hashedString).to.be.a('string');
|
|
24
24
|
expect(hashedString.length).to.be.at.least(10);
|
|
25
25
|
});
|
|
@@ -29,7 +29,7 @@ export declare const getStateSetProxyHandler: (element: StatefulComponentAny) =>
|
|
|
29
29
|
get: (set: Set<unknown>, key: string | symbol) => any;
|
|
30
30
|
has(set: Set<unknown>, key: string | symbol): boolean;
|
|
31
31
|
};
|
|
32
|
-
export declare function proxyObjectContent<O extends object>(element: StatefulComponentAny, object: O): void;
|
|
33
|
-
export declare function proxyArrayContent<A extends unknown[]>(element: StatefulComponentAny, array: A): void;
|
|
32
|
+
export declare function proxyObjectContent<O extends object>(element: StatefulComponentAny, object: O, adopted?: WeakMap<object, object>): void;
|
|
33
|
+
export declare function proxyArrayContent<A extends unknown[]>(element: StatefulComponentAny, array: A, adopted?: WeakMap<object, object>): void;
|
|
34
34
|
export {};
|
|
35
35
|
//# sourceMappingURL=componentProxyHandlers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"componentProxyHandlers.d.ts","sourceRoot":"","sources":["../src/componentProxyHandlers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"componentProxyHandlers.d.ts","sourceRoot":"","sources":["../src/componentProxyHandlers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAQ/E,KAAK,0BAA0B,CAAC,KAAK,SAAS,MAAM,IAC9C,yBAAyB,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,GAC1C,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;AAExC,KAAK,oBAAoB,GAAG,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAEvG,eAAO,MAAM,SAAS,eAAqB,CAAA;AAC3C,eAAO,MAAM,UAAU,eAAsB,CAAA;AAE7C,eAAO,MAAM,oBAAoB,GAAI,KAAK,SAAS,MAAM,EACxD,SAAS,0BAA0B,CAAC,KAAK,CAAC;eAC5B,KAAK,OAAO,MAAM,GAAG,MAAM;eAQ3B,KAAK,OAAO,MAAM,GAAG,MAAM,SAAS,OAAO;0BAmChC,KAAK,OAAO,MAAM,GAAG,MAAM;aAOxC,KAAK,OAAO,MAAM,GAAG,MAAM;CAItC,CAAA;AAGF,eAAO,MAAM,0BAA0B,GAAI,CAAC,SAAS,MAAM,EAAE,SAAS,oBAAoB;eAC3E,CAAC,OAAO,MAAM,GAAG,MAAM;eAQvB,CAAC,OAAO,MAAM,GAAG,MAAM,SAAS,OAAO;0BAkC5B,CAAC,OAAO,MAAM,GAAG,MAAM;aAOpC,CAAC,OAAO,MAAM,GAAG,MAAM;CAIlC,CAAA;AAGF,eAAO,MAAM,yBAAyB,GAAI,SAAS,oBAAoB;eA8BpD,OAAO,EAAE,OAAO,MAAM,GAAG,MAAM;eA7B5B,OAAO,EAAE,OAAO,MAAM,GAAG,MAAM,SAAS,OAAO;aAgEpD,OAAO,EAAE,OAAO,MAAM,GAAG,MAAM;wBAIpB,OAAO,EAAE,OAAO,MAAM,GAAG,MAAM;CAU1D,CAAA;AAID,eAAO,MAAM,uBAAuB,GAAI,SAAS,oBAAoB;eACtD,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,MAAM,GAAG,MAAM;aA+C7C,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,MAAM,GAAG,MAAM;CAItD,CAAA;AAIF,eAAO,MAAM,uBAAuB,GAAI,SAAS,oBAAoB;eACtD,GAAG,CAAC,OAAO,CAAC,OAAO,MAAM,GAAG,MAAM;aA0CpC,GAAG,CAAC,OAAO,CAAC,OAAO,MAAM,GAAG,MAAM;CAI7C,CAAA;AAgCF,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,EAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,GAAE,OAAO,CAAC,MAAM,EAAE,MAAM,CAAiB,GAC3F,IAAI,CAiCN;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,EACjD,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,GAAE,OAAO,CAAC,MAAM,EAAE,MAAM,CAAiB,GAC1F,IAAI,CAkCN"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { KeyIsArrayMutatingFunction, KeyIsMapMutatingFunction, KeyIsSetMutatingFunction } from "./mutatingFunctions.js";
|
|
2
2
|
import { objectTypesToProxy } from "./objectTypesToProxy.js";
|
|
3
|
-
import {
|
|
3
|
+
import { warn } from "./output.js";
|
|
4
|
+
import { isDev } from "./isDev.js";
|
|
4
5
|
export const $original = Symbol('original');
|
|
5
6
|
export const $ownerName = Symbol('ownerName');
|
|
6
7
|
export const getStateProxyHandler = element => ({
|
|
@@ -31,9 +32,9 @@ export const getStateProxyHandler = element => ({
|
|
|
31
32
|
} else if (value instanceof Map) {
|
|
32
33
|
// Maps
|
|
33
34
|
//console.log('found a Map in state');
|
|
34
|
-
obj[key]
|
|
35
|
+
obj[key]
|
|
35
36
|
//= new Proxy(value, getStateMapProxyHandler(element))
|
|
36
|
-
proxyMap(value, element);
|
|
37
|
+
= proxyMap(value, element);
|
|
37
38
|
} else if (value instanceof Set) {
|
|
38
39
|
// Sets
|
|
39
40
|
//console.log('found a Set in state');
|
|
@@ -64,6 +65,9 @@ export const getStateProxyHandler = element => ({
|
|
|
64
65
|
export const getStateObjectProxyHandler = element => ({
|
|
65
66
|
get: (obj, key) => {
|
|
66
67
|
if (key === '__original') return obj; // return the internal object if requested
|
|
68
|
+
if (key === $original) return obj; // return the internal object if requested
|
|
69
|
+
// owner?
|
|
70
|
+
if (key === $ownerName) return element.constructor.name;
|
|
67
71
|
return obj[key]; // Proxy typing allowed accessing keys outside of the type.
|
|
68
72
|
// So act on that obj as an object that accept all string | symbol keys
|
|
69
73
|
},
|
|
@@ -317,7 +321,7 @@ export const getStateSetProxyHandler = element => ({
|
|
|
317
321
|
// }
|
|
318
322
|
// }
|
|
319
323
|
// }
|
|
320
|
-
export function proxyObjectContent(element, object) {
|
|
324
|
+
export function proxyObjectContent(element, object, adopted = new WeakMap()) {
|
|
321
325
|
//console.log('proxying object content', object, 'for', element.constructor.name)
|
|
322
326
|
for (const key in object) {
|
|
323
327
|
//console.log('proxy key', key)
|
|
@@ -327,34 +331,33 @@ export function proxyObjectContent(element, object) {
|
|
|
327
331
|
if (Array.isArray(value)) {
|
|
328
332
|
object[key]
|
|
329
333
|
//= new Proxy(value, getStateObjectProxyHandler(element))
|
|
330
|
-
= proxyArray(value, element);
|
|
334
|
+
= proxyArray(value, element, adopted);
|
|
331
335
|
} else if (typeof value === 'object' && value !== null
|
|
332
336
|
// && value.constructor.name === 'Object'
|
|
333
337
|
&& objectTypesToProxy.includes(value.constructor.name) // ensure that it is an object literal or otherwise an approve object type
|
|
334
338
|
) {
|
|
335
339
|
// plain objects
|
|
336
340
|
//proxyObjectContent(element, value)
|
|
337
|
-
// check that this thing hasn't already been proxied somewhere?
|
|
338
341
|
object[key]
|
|
339
342
|
//= new Proxy(value, getStateObjectProxyHandler(element))
|
|
340
|
-
= proxyObject(value, element);
|
|
343
|
+
= proxyObject(value, element, adopted);
|
|
341
344
|
} else if (value instanceof Map) {
|
|
342
345
|
// Maps
|
|
343
346
|
//console.log('proxying a Map in state');
|
|
344
347
|
object[key]
|
|
345
348
|
//= new Proxy(value, getStateMapProxyHandler(element))
|
|
346
|
-
= proxyMap(value, element);
|
|
349
|
+
= proxyMap(value, element, adopted);
|
|
347
350
|
} else if (value instanceof Set) {
|
|
348
351
|
// Sets
|
|
349
352
|
//console.log('proxying a Set in state');
|
|
350
353
|
object[key]
|
|
351
354
|
//= new Proxy(value, getStateSetProxyHandler(element))
|
|
352
|
-
= proxySet(value, element);
|
|
355
|
+
= proxySet(value, element, adopted);
|
|
353
356
|
}
|
|
354
357
|
}
|
|
355
358
|
}
|
|
356
359
|
}
|
|
357
|
-
export function proxyArrayContent(element, array) {
|
|
360
|
+
export function proxyArrayContent(element, array, adopted = new WeakMap()) {
|
|
358
361
|
//console.log('proxying array content', array, 'for', element.constructor.name)
|
|
359
362
|
for (const key in array) {
|
|
360
363
|
//console.log('proxying arr key', key)
|
|
@@ -365,7 +368,7 @@ export function proxyArrayContent(element, array) {
|
|
|
365
368
|
//proxyArrayContent(element, value)
|
|
366
369
|
array[key]
|
|
367
370
|
//= new Proxy(value, getStateObjectProxyHandler(element))
|
|
368
|
-
= proxyArray(value, element);
|
|
371
|
+
= proxyArray(value, element, adopted);
|
|
369
372
|
} else if (typeof value === 'object' && value !== null
|
|
370
373
|
// && value.constructor.name === 'Object'
|
|
371
374
|
&& objectTypesToProxy.includes(value.constructor.name) // ensure that it is an object literal or otherwise an approve object type
|
|
@@ -374,58 +377,81 @@ export function proxyArrayContent(element, array) {
|
|
|
374
377
|
//proxyObjectContent(element, value)
|
|
375
378
|
array[key]
|
|
376
379
|
//= new Proxy(value, getStateObjectProxyHandler(element))
|
|
377
|
-
= proxyObject(value, element);
|
|
380
|
+
= proxyObject(value, element, adopted);
|
|
378
381
|
} else if (value instanceof Map) {
|
|
379
382
|
// Maps
|
|
380
383
|
//console.log('proxying a Map in state');
|
|
381
384
|
array[key]
|
|
382
385
|
//= new Proxy(value, getStateMapProxyHandler(element))
|
|
383
|
-
= proxyMap(value, element);
|
|
386
|
+
= proxyMap(value, element, adopted);
|
|
384
387
|
} else if (value instanceof Set) {
|
|
385
388
|
// Sets
|
|
386
389
|
//console.log('proxying a Set in state');
|
|
387
390
|
array[key]
|
|
388
391
|
//= new Proxy(value, getStateSetProxyHandler(element))
|
|
389
|
-
= proxySet(value, element);
|
|
392
|
+
= proxySet(value, element, adopted);
|
|
390
393
|
}
|
|
391
394
|
}
|
|
392
395
|
}
|
|
393
396
|
}
|
|
394
|
-
|
|
397
|
+
// An object may only live in one component's state. Anything arriving that is already owned
|
|
398
|
+
// (i.e. is one of our state proxies) is adopted as a shallow copy of its original, so the two
|
|
399
|
+
// components never alias. Shallow is deliberate: the caller walks the contents and re-enters
|
|
400
|
+
// this for each owned child, so the existing recursion does the deep work.
|
|
401
|
+
function adoptIntoState(value, element) {
|
|
402
|
+
if (!($original in value && $ownerName in value)) return value;
|
|
403
|
+
if (isDev) warn(`${element.constructor.name} stored an object already owned by` + ` ${String(value[$ownerName])}'s state — a copy was taken.`);
|
|
404
|
+
const original = value[$original]; // the raw target, not via the get traps —
|
|
405
|
+
// spreading the proxy would yield proxied children
|
|
406
|
+
if (Array.isArray(original)) return [...original];
|
|
407
|
+
if (original instanceof Set) return new Set(original);
|
|
408
|
+
if (original instanceof Map) return new Map(original);
|
|
409
|
+
return {
|
|
410
|
+
...original
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
// `adopted` maps each raw original seen in this pass to the proxy that replaced it. Registered
|
|
414
|
+
// before recursing into contents, so cycles terminate and an object referenced twice in one
|
|
415
|
+
// state tree stays one object instead of silently becoming two.
|
|
416
|
+
function proxyObject(obj, element, adopted = new WeakMap()) {
|
|
395
417
|
//console.log('proxyObject', obj, element.constructor.name)
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return new Proxy(obj, getStateObjectProxyHandler(element)); // if an plan object was set, proxy it too. If an object has a class other than Object, we'll leave it alone
|
|
418
|
+
const raw = $original in obj ? obj[$original] : obj;
|
|
419
|
+
const existing = adopted.get(raw);
|
|
420
|
+
if (existing) return existing;
|
|
421
|
+
obj = adoptIntoState(obj, element);
|
|
422
|
+
const proxy = new Proxy(obj, getStateObjectProxyHandler(element)); // if an plan object was set, proxy it too. If an object has a class other than Object, we'll leave it alone
|
|
423
|
+
adopted.set(raw, proxy);
|
|
424
|
+
proxyObjectContent(element, obj, adopted);
|
|
425
|
+
return proxy;
|
|
405
426
|
}
|
|
406
|
-
function proxyArray(arr, element) {
|
|
427
|
+
function proxyArray(arr, element, adopted = new WeakMap()) {
|
|
407
428
|
//console.log('proxyArray')
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
return new Proxy(arr, getStateArrayProxyHandler(element));
|
|
429
|
+
const raw = $original in arr ? arr[$original] : arr;
|
|
430
|
+
const existing = adopted.get(raw);
|
|
431
|
+
if (existing) return existing;
|
|
432
|
+
arr = adoptIntoState(arr, element);
|
|
433
|
+
const proxy = new Proxy(arr, getStateArrayProxyHandler(element));
|
|
434
|
+
adopted.set(raw, proxy);
|
|
435
|
+
proxyArrayContent(element, arr, adopted);
|
|
436
|
+
return proxy;
|
|
417
437
|
}
|
|
418
|
-
function proxySet(set, element) {
|
|
438
|
+
function proxySet(set, element, adopted = new WeakMap()) {
|
|
419
439
|
//console.log('proxySet')
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
440
|
+
const raw = $original in set ? set[$original] : set;
|
|
441
|
+
const existing = adopted.get(raw);
|
|
442
|
+
if (existing) return existing;
|
|
443
|
+
set = adoptIntoState(set, element);
|
|
444
|
+
const proxy = new Proxy(set, getStateSetProxyHandler(element));
|
|
445
|
+
adopted.set(raw, proxy);
|
|
446
|
+
return proxy;
|
|
424
447
|
}
|
|
425
|
-
function proxyMap(map, element) {
|
|
448
|
+
function proxyMap(map, element, adopted = new WeakMap()) {
|
|
426
449
|
//console.log('proxyMap')
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
450
|
+
const raw = $original in map ? map[$original] : map;
|
|
451
|
+
const existing = adopted.get(raw);
|
|
452
|
+
if (existing) return existing;
|
|
453
|
+
map = adoptIntoState(map, element);
|
|
454
|
+
const proxy = new Proxy(map, getStateMapProxyHandler(element));
|
|
455
|
+
adopted.set(raw, proxy);
|
|
456
|
+
return proxy;
|
|
431
457
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"componentProxyHandlers.sxyt.d.ts","sourceRoot":"","sources":["../src/componentProxyHandlers.sxyt.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
2
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
3
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
4
|
+
import { describe } from 'sxy-test-runner';
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
import { proxyObjectContent, getStateProxyHandler, $original, $ownerName } from "./componentProxyHandlers.js";
|
|
7
|
+
class FakeComponent {
|
|
8
|
+
constructor() {
|
|
9
|
+
_defineProperty(this, "__initialized", true);
|
|
10
|
+
_defineProperty(this, "updates", 0);
|
|
11
|
+
}
|
|
12
|
+
__stateUpdated() {
|
|
13
|
+
this.updates++;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class ParentComp extends FakeComponent {}
|
|
17
|
+
class ChildComp extends FakeComponent {}
|
|
18
|
+
// proxyObjectContent proxies a state object's contents in place, the same entry point
|
|
19
|
+
// Components.__init uses, so state.<key> afterwards is a state-object proxy
|
|
20
|
+
function makeState(element, state) {
|
|
21
|
+
proxyObjectContent(element, state);
|
|
22
|
+
return state;
|
|
23
|
+
}
|
|
24
|
+
describe('componentProxyHandlers state adoption', ({
|
|
25
|
+
it
|
|
26
|
+
}) => {
|
|
27
|
+
it('exposes $original and $ownerName on state object proxies', () => {
|
|
28
|
+
const parent = new ParentComp();
|
|
29
|
+
const state = makeState(parent, {
|
|
30
|
+
data: {
|
|
31
|
+
x: 1
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
expect(state.data[$ownerName]).to.equal('ParentComp');
|
|
35
|
+
expect(state.data[$original]).to.deep.equal({
|
|
36
|
+
x: 1
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it('adopts an object owned by another component as a copy instead of throwing', () => {
|
|
40
|
+
const parent = new ParentComp();
|
|
41
|
+
const parentState = makeState(parent, {
|
|
42
|
+
data: {
|
|
43
|
+
x: 1,
|
|
44
|
+
nested: {
|
|
45
|
+
y: 2
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const child = new ChildComp();
|
|
50
|
+
const childState = makeState(child, {
|
|
51
|
+
data: parentState.data
|
|
52
|
+
});
|
|
53
|
+
expect(childState.data).to.not.equal(parentState.data);
|
|
54
|
+
expect(childState.data.x).to.equal(1);
|
|
55
|
+
expect(childState.data.nested.y).to.equal(2);
|
|
56
|
+
// the two components must not alias, at any depth
|
|
57
|
+
childState.data.x = 99;
|
|
58
|
+
childState.data.nested.y = 55;
|
|
59
|
+
expect(parentState.data.x).to.equal(1);
|
|
60
|
+
expect(parentState.data.nested.y).to.equal(2);
|
|
61
|
+
expect(parent.updates).to.equal(0);
|
|
62
|
+
expect(child.updates).to.be.greaterThan(0);
|
|
63
|
+
});
|
|
64
|
+
it('adopts via the top-level state set trap too', () => {
|
|
65
|
+
const parent = new ParentComp();
|
|
66
|
+
const parentState = makeState(parent, {
|
|
67
|
+
list: [1, {
|
|
68
|
+
deep: true
|
|
69
|
+
}]
|
|
70
|
+
});
|
|
71
|
+
const child = new ChildComp();
|
|
72
|
+
const childStateTarget = {
|
|
73
|
+
list: undefined
|
|
74
|
+
};
|
|
75
|
+
const childState = new Proxy(childStateTarget, getStateProxyHandler(child));
|
|
76
|
+
childState.list = parentState.list;
|
|
77
|
+
expect(childState.list).to.not.equal(parentState.list);
|
|
78
|
+
childState.list.push(2);
|
|
79
|
+
expect(childState.list.length).to.equal(3);
|
|
80
|
+
expect(parentState.list.length).to.equal(2);
|
|
81
|
+
});
|
|
82
|
+
it('preserves shared references within one state tree', () => {
|
|
83
|
+
const el = new ParentComp();
|
|
84
|
+
const shared = {
|
|
85
|
+
v: 1
|
|
86
|
+
};
|
|
87
|
+
const state = makeState(el, {
|
|
88
|
+
a: shared,
|
|
89
|
+
b: shared
|
|
90
|
+
});
|
|
91
|
+
expect(state.a).to.equal(state.b);
|
|
92
|
+
state.a.v = 2;
|
|
93
|
+
expect(state.b.v).to.equal(2);
|
|
94
|
+
});
|
|
95
|
+
it('terminates on self-referencing objects', () => {
|
|
96
|
+
const el = new ParentComp();
|
|
97
|
+
const cyc = {
|
|
98
|
+
n: 1
|
|
99
|
+
};
|
|
100
|
+
cyc.self = cyc;
|
|
101
|
+
const state = makeState(el, {
|
|
102
|
+
cyc
|
|
103
|
+
});
|
|
104
|
+
expect(state.cyc.self).to.equal(state.cyc);
|
|
105
|
+
expect(state.cyc.self.self.n).to.equal(1);
|
|
106
|
+
});
|
|
107
|
+
it('adopts Maps and Sets as copies', () => {
|
|
108
|
+
const parent = new ParentComp();
|
|
109
|
+
const parentState = makeState(parent, {
|
|
110
|
+
m: new Map([['k', 1]]),
|
|
111
|
+
s: new Set([1, 2])
|
|
112
|
+
});
|
|
113
|
+
const child = new ChildComp();
|
|
114
|
+
const childState = makeState(child, {
|
|
115
|
+
m: parentState.m,
|
|
116
|
+
s: parentState.s
|
|
117
|
+
});
|
|
118
|
+
childState.m.set('k', 2);
|
|
119
|
+
childState.s.add(3);
|
|
120
|
+
expect(parentState.m.get('k')).to.equal(1);
|
|
121
|
+
expect(parentState.s.has(3)).to.equal(false);
|
|
122
|
+
});
|
|
123
|
+
it('assigns a Map set into top-level state (missing assignment fix)', () => {
|
|
124
|
+
const el = new ParentComp();
|
|
125
|
+
const state = new Proxy({}, getStateProxyHandler(el));
|
|
126
|
+
state.m = new Map([['k', 1]]);
|
|
127
|
+
expect(state.m).to.not.equal(undefined);
|
|
128
|
+
expect(state.m.get('k')).to.equal(1);
|
|
129
|
+
});
|
|
130
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tungsten",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"rmrf": "^2.0.4",
|
|
132
132
|
"rollup-plugin-visualizer": "^5.14.0",
|
|
133
133
|
"supertest": "^6.3.4",
|
|
134
|
-
"sxy-test-runner": "^
|
|
134
|
+
"sxy-test-runner": "^2.2.2",
|
|
135
135
|
"sxy-types": "^1.0.23",
|
|
136
136
|
"typescript": "^6.0.3",
|
|
137
137
|
"webpack-bundle-analyzer": "^4.10.2",
|
|
@@ -163,6 +163,7 @@
|
|
|
163
163
|
"build-ts-once": "tsc",
|
|
164
164
|
"build-babel": "babel intermed --out-dir dist --ignore \"**/*.cjs\" --copy-files --config-file ./babel.framework.config.js --watch",
|
|
165
165
|
"build-babel-once": "babel intermed --out-dir dist --ignore \"**/*.cjs\" --copy-files --config-file ./babel.framework.config.js",
|
|
166
|
-
"test": "sxy-test-runner"
|
|
166
|
+
"test": "sxy-test-runner",
|
|
167
|
+
"test-once": "sxy-test-runner once"
|
|
167
168
|
}
|
|
168
169
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createTestApp.sxyt.d.ts","sourceRoot":"","sources":["../../src/testing/createTestApp.sxyt.tsx"],"names":[],"mappings":""}
|
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
2
|
-
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
3
|
-
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
4
|
-
import { expect } from 'chai';
|
|
5
|
-
import { Window } from 'happy-dom';
|
|
6
|
-
import { describe } from 'sxy-test-runner';
|
|
7
|
-
import { TungstenBaseSimpleStoreComponent, TungstenSimpleComponent, TungstenStatefulComponent } from "../Components.js";
|
|
8
|
-
import { registerStore } from "../Store/registerStore.js";
|
|
9
|
-
import { createTestApp, testStore } from "./index.js";
|
|
10
|
-
import { jsxs as _jsxs, jsx as _jsx } from "tungsten/jsxRuntime.js";
|
|
11
|
-
class Counter extends TungstenStatefulComponent {
|
|
12
|
-
constructor(...args) {
|
|
13
|
-
super(...args);
|
|
14
|
-
_defineProperty(this, "state", {
|
|
15
|
-
count: 0
|
|
16
|
-
});
|
|
17
|
-
_defineProperty(this, "clientInitialized", false);
|
|
18
|
-
_defineProperty(this, "clientInit", () => {
|
|
19
|
-
this.clientInitialized = true;
|
|
20
|
-
});
|
|
21
|
-
_defineProperty(this, "content", () => _jsxs("button", {
|
|
22
|
-
onClick: () => {
|
|
23
|
-
this.state.count++;
|
|
24
|
-
},
|
|
25
|
-
children: [this.props.label, ":", this.state.count]
|
|
26
|
-
}));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
class TestStoreSchema {
|
|
30
|
-
constructor() {
|
|
31
|
-
_defineProperty(this, "value", 0);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const testStoreRegistration = registerStore(TestStoreSchema);
|
|
35
|
-
class StoreRoot extends TungstenBaseSimpleStoreComponent {
|
|
36
|
-
constructor(...args) {
|
|
37
|
-
super(...args);
|
|
38
|
-
_defineProperty(this, "store", testStoreRegistration.getComponentStore(this));
|
|
39
|
-
_defineProperty(this, "init", () => {
|
|
40
|
-
this.store.value.subscribe();
|
|
41
|
-
});
|
|
42
|
-
_defineProperty(this, "content", () => _jsx("p", {
|
|
43
|
-
children: this.store.value.get()
|
|
44
|
-
}));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
describe('createTestApp()', ({
|
|
48
|
-
it
|
|
49
|
-
}) => {
|
|
50
|
-
it('mounts, resolves updates, preserves the root for props, and unmounts', async () => {
|
|
51
|
-
const window = new Window();
|
|
52
|
-
const document = window.document;
|
|
53
|
-
const testApp = await createTestApp(Counter, {
|
|
54
|
-
document,
|
|
55
|
-
props: {
|
|
56
|
-
label: 'Count'
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
const root = testApp.component;
|
|
60
|
-
expect(root.clientInitialized).to.equal(true);
|
|
61
|
-
expect(testApp.container.textContent).to.equal('Count:0');
|
|
62
|
-
const button = testApp.container.querySelector('button');
|
|
63
|
-
expect(button).not.to.equal(null);
|
|
64
|
-
button.dispatchEvent(new window.Event('click', {
|
|
65
|
-
bubbles: true
|
|
66
|
-
}));
|
|
67
|
-
await testApp.resolved();
|
|
68
|
-
expect(testApp.container.textContent).to.equal('Count:1');
|
|
69
|
-
await testApp.setProps({
|
|
70
|
-
label: 'Total'
|
|
71
|
-
});
|
|
72
|
-
expect(testApp.component).to.equal(root);
|
|
73
|
-
expect(testApp.container.textContent).to.equal('Total:1');
|
|
74
|
-
const container = testApp.container;
|
|
75
|
-
await testApp.unmount();
|
|
76
|
-
await testApp.unmount();
|
|
77
|
-
expect(container.isConnected).to.equal(false);
|
|
78
|
-
});
|
|
79
|
-
it('tracks asynchronous event work', async () => {
|
|
80
|
-
const window = new Window();
|
|
81
|
-
const document = window.document;
|
|
82
|
-
let release;
|
|
83
|
-
const released = new Promise(resolve => {
|
|
84
|
-
release = resolve;
|
|
85
|
-
});
|
|
86
|
-
class AsyncButton extends TungstenStatefulComponent {
|
|
87
|
-
constructor(...args) {
|
|
88
|
-
super(...args);
|
|
89
|
-
_defineProperty(this, "state", {
|
|
90
|
-
finished: false
|
|
91
|
-
});
|
|
92
|
-
_defineProperty(this, "content", () => _jsx("button", {
|
|
93
|
-
onClick: async () => {
|
|
94
|
-
await released;
|
|
95
|
-
this.state.finished = true;
|
|
96
|
-
},
|
|
97
|
-
children: this.state.finished ? 'finished' : 'waiting'
|
|
98
|
-
}));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const testApp = await createTestApp(AsyncButton, {
|
|
102
|
-
document
|
|
103
|
-
});
|
|
104
|
-
testApp.container.querySelector('button').dispatchEvent(new window.Event('click'));
|
|
105
|
-
release();
|
|
106
|
-
await testApp.resolved();
|
|
107
|
-
expect(testApp.container.textContent).to.equal('finished');
|
|
108
|
-
await testApp.unmount();
|
|
109
|
-
});
|
|
110
|
-
it('settles asynchronous lifecycle-triggered updates', async () => {
|
|
111
|
-
const window = new Window();
|
|
112
|
-
const document = window.document;
|
|
113
|
-
class LifecycleRoot extends TungstenStatefulComponent {
|
|
114
|
-
constructor(...args) {
|
|
115
|
-
super(...args);
|
|
116
|
-
_defineProperty(this, "state", {
|
|
117
|
-
phase: 'mounting'
|
|
118
|
-
});
|
|
119
|
-
_defineProperty(this, "componentDidMount", async () => {
|
|
120
|
-
await Promise.resolve();
|
|
121
|
-
this.state.phase = 'ready';
|
|
122
|
-
});
|
|
123
|
-
_defineProperty(this, "componentDidUpdate", async () => {
|
|
124
|
-
if (this.state.phase !== 'clicked') return;
|
|
125
|
-
await Promise.resolve();
|
|
126
|
-
this.state.phase = 'settled';
|
|
127
|
-
});
|
|
128
|
-
_defineProperty(this, "content", () => _jsx("button", {
|
|
129
|
-
onClick: () => {
|
|
130
|
-
this.state.phase = 'clicked';
|
|
131
|
-
},
|
|
132
|
-
children: this.state.phase
|
|
133
|
-
}));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
const testApp = await createTestApp(LifecycleRoot, {
|
|
137
|
-
document
|
|
138
|
-
});
|
|
139
|
-
expect(testApp.container.textContent).to.equal('ready');
|
|
140
|
-
testApp.container.querySelector('button').dispatchEvent(new window.Event('click'));
|
|
141
|
-
await testApp.resolved();
|
|
142
|
-
expect(testApp.container.textContent).to.equal('settled');
|
|
143
|
-
await testApp.unmount();
|
|
144
|
-
});
|
|
145
|
-
it('initializes independent real stores before mounting', async () => {
|
|
146
|
-
const firstWindow = new Window();
|
|
147
|
-
const secondWindow = new Window();
|
|
148
|
-
const first = await createTestApp(StoreRoot, {
|
|
149
|
-
document: firstWindow.document,
|
|
150
|
-
stores: [testStore(testStoreRegistration, store => {
|
|
151
|
-
store.value.set(1);
|
|
152
|
-
})]
|
|
153
|
-
});
|
|
154
|
-
const second = await createTestApp(StoreRoot, {
|
|
155
|
-
document: secondWindow.document,
|
|
156
|
-
stores: [testStore(testStoreRegistration, store => {
|
|
157
|
-
store.value.set(2);
|
|
158
|
-
})]
|
|
159
|
-
});
|
|
160
|
-
expect(first.container.textContent).to.equal('1');
|
|
161
|
-
expect(second.container.textContent).to.equal('2');
|
|
162
|
-
first.component.store.value.set(3);
|
|
163
|
-
await first.resolved();
|
|
164
|
-
expect(first.container.textContent).to.equal('3');
|
|
165
|
-
expect(second.container.textContent).to.equal('2');
|
|
166
|
-
await first.unmount();
|
|
167
|
-
await second.unmount();
|
|
168
|
-
});
|
|
169
|
-
it('recursively unmounts components below HTML elements', async () => {
|
|
170
|
-
const window = new Window();
|
|
171
|
-
const document = window.document;
|
|
172
|
-
const lifecycle = [];
|
|
173
|
-
class Child extends TungstenSimpleComponent {
|
|
174
|
-
constructor(...args) {
|
|
175
|
-
super(...args);
|
|
176
|
-
_defineProperty(this, "content", () => _jsx("span", {
|
|
177
|
-
children: "child"
|
|
178
|
-
}));
|
|
179
|
-
_defineProperty(this, "componentWillUnmount", () => {
|
|
180
|
-
lifecycle.push('will');
|
|
181
|
-
});
|
|
182
|
-
_defineProperty(this, "componentDidUnmount", () => {
|
|
183
|
-
lifecycle.push('did');
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
class Root extends TungstenSimpleComponent {
|
|
188
|
-
constructor(...args) {
|
|
189
|
-
super(...args);
|
|
190
|
-
_defineProperty(this, "content", () => _jsx("main", {
|
|
191
|
-
children: _jsx(Child, {})
|
|
192
|
-
}));
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
const container = window.document.createElement('section');
|
|
196
|
-
window.document.body.appendChild(container);
|
|
197
|
-
const testApp = await createTestApp(Root, {
|
|
198
|
-
document,
|
|
199
|
-
container: container
|
|
200
|
-
});
|
|
201
|
-
await testApp.unmount();
|
|
202
|
-
expect(lifecycle).to.deep.equal(['will', 'did']);
|
|
203
|
-
expect(window.document.body.contains(container)).to.equal(true);
|
|
204
|
-
expect(container.childNodes.length).to.equal(0);
|
|
205
|
-
});
|
|
206
|
-
it('rejects a non-empty container without modifying it', async () => {
|
|
207
|
-
const window = new Window();
|
|
208
|
-
const document = window.document;
|
|
209
|
-
const container = window.document.createElement('div');
|
|
210
|
-
container.textContent = 'existing';
|
|
211
|
-
let error;
|
|
212
|
-
try {
|
|
213
|
-
await createTestApp(Counter, {
|
|
214
|
-
document,
|
|
215
|
-
container: container,
|
|
216
|
-
props: {
|
|
217
|
-
label: 'Count'
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
} catch (caught) {
|
|
221
|
-
error = caught;
|
|
222
|
-
}
|
|
223
|
-
expect(error).to.be.instanceOf(Error);
|
|
224
|
-
expect(container.textContent).to.equal('existing');
|
|
225
|
-
});
|
|
226
|
-
it('rolls back partially mounted descendants after an error', async () => {
|
|
227
|
-
const window = new Window();
|
|
228
|
-
const document = window.document;
|
|
229
|
-
const lifecycle = [];
|
|
230
|
-
class MountedChild extends TungstenSimpleComponent {
|
|
231
|
-
constructor(...args) {
|
|
232
|
-
super(...args);
|
|
233
|
-
_defineProperty(this, "content", () => _jsx("span", {
|
|
234
|
-
children: "mounted"
|
|
235
|
-
}));
|
|
236
|
-
_defineProperty(this, "componentDidMount", () => {
|
|
237
|
-
lifecycle.push('mount');
|
|
238
|
-
});
|
|
239
|
-
_defineProperty(this, "componentWillUnmount", () => {
|
|
240
|
-
lifecycle.push('will');
|
|
241
|
-
});
|
|
242
|
-
_defineProperty(this, "componentDidUnmount", () => {
|
|
243
|
-
lifecycle.push('did');
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
class BrokenChild extends TungstenSimpleComponent {
|
|
248
|
-
constructor(...args) {
|
|
249
|
-
super(...args);
|
|
250
|
-
_defineProperty(this, "content", () => {
|
|
251
|
-
throw new Error('broken child');
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
class BrokenRoot extends TungstenSimpleComponent {
|
|
256
|
-
constructor(...args) {
|
|
257
|
-
super(...args);
|
|
258
|
-
_defineProperty(this, "content", () => _jsxs("main", {
|
|
259
|
-
children: [_jsx(MountedChild, {}), _jsx(BrokenChild, {})]
|
|
260
|
-
}));
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
const originalBodyChildren = window.document.body.childNodes.length;
|
|
264
|
-
let error;
|
|
265
|
-
try {
|
|
266
|
-
await createTestApp(BrokenRoot, {
|
|
267
|
-
document
|
|
268
|
-
});
|
|
269
|
-
} catch (caught) {
|
|
270
|
-
error = caught;
|
|
271
|
-
}
|
|
272
|
-
expect(error).to.be.instanceOf(Error);
|
|
273
|
-
expect(lifecycle).to.deep.equal(['mount', 'will', 'did']);
|
|
274
|
-
expect(window.document.body.childNodes.length).to.equal(originalBodyChildren);
|
|
275
|
-
});
|
|
276
|
-
it('supports input, checkbox, and select bindings', async () => {
|
|
277
|
-
const window = new Window();
|
|
278
|
-
const document = window.document;
|
|
279
|
-
class FormRoot extends TungstenStatefulComponent {
|
|
280
|
-
constructor(...args) {
|
|
281
|
-
super(...args);
|
|
282
|
-
_defineProperty(this, "state", {
|
|
283
|
-
name: 'Robert',
|
|
284
|
-
checked: false,
|
|
285
|
-
choice: 'b'
|
|
286
|
-
});
|
|
287
|
-
_defineProperty(this, "content", () => _jsxs("form", {
|
|
288
|
-
children: [_jsx("input", {
|
|
289
|
-
name: "name",
|
|
290
|
-
bind: [this.state, 'name']
|
|
291
|
-
}), _jsx("input", {
|
|
292
|
-
name: "enabled",
|
|
293
|
-
type: "checkbox",
|
|
294
|
-
bindChecked: [this.state, 'checked']
|
|
295
|
-
}), _jsxs("select", {
|
|
296
|
-
name: "choice",
|
|
297
|
-
bind: [this.state, 'choice'],
|
|
298
|
-
children: [_jsx("option", {
|
|
299
|
-
value: "a",
|
|
300
|
-
children: "A"
|
|
301
|
-
}), _jsx("option", {
|
|
302
|
-
value: "b",
|
|
303
|
-
children: "B"
|
|
304
|
-
}), _jsx("option", {
|
|
305
|
-
value: "c",
|
|
306
|
-
children: "C"
|
|
307
|
-
})]
|
|
308
|
-
}), _jsxs("output", {
|
|
309
|
-
children: [this.state.name, ":", String(this.state.checked), ":", this.state.choice]
|
|
310
|
-
})]
|
|
311
|
-
}));
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
const testApp = await createTestApp(FormRoot, {
|
|
315
|
-
document
|
|
316
|
-
});
|
|
317
|
-
const name = testApp.container.querySelector('input[name="name"]');
|
|
318
|
-
const enabled = testApp.container.querySelector('input[name="enabled"]');
|
|
319
|
-
const choice = testApp.container.querySelector('select');
|
|
320
|
-
name.value = 'Alice';
|
|
321
|
-
name.dispatchEvent(new window.Event('input', {
|
|
322
|
-
bubbles: true
|
|
323
|
-
}));
|
|
324
|
-
enabled.checked = true;
|
|
325
|
-
enabled.dispatchEvent(new window.Event('change', {
|
|
326
|
-
bubbles: true
|
|
327
|
-
}));
|
|
328
|
-
choice.value = 'c';
|
|
329
|
-
choice.dispatchEvent(new window.Event('input', {
|
|
330
|
-
bubbles: true
|
|
331
|
-
}));
|
|
332
|
-
await testApp.resolved();
|
|
333
|
-
expect(testApp.component.state.name).to.equal('Alice');
|
|
334
|
-
expect(testApp.component.state.checked).to.equal(true);
|
|
335
|
-
expect(testApp.component.state.choice).to.equal('c');
|
|
336
|
-
expect(testApp.container.querySelector('output').textContent).to.equal('Alice:true:c');
|
|
337
|
-
await testApp.unmount();
|
|
338
|
-
});
|
|
339
|
-
it('preserves keyed nodes and controlled select values during reconciliation', async () => {
|
|
340
|
-
const window = new Window();
|
|
341
|
-
const document = window.document;
|
|
342
|
-
class ReconciliationRoot extends TungstenStatefulComponent {
|
|
343
|
-
constructor(...args) {
|
|
344
|
-
super(...args);
|
|
345
|
-
_defineProperty(this, "state", {
|
|
346
|
-
items: ['a', 'b', 'c'],
|
|
347
|
-
selected: 'b',
|
|
348
|
-
updates: 0
|
|
349
|
-
});
|
|
350
|
-
_defineProperty(this, "content", () => _jsxs("main", {
|
|
351
|
-
children: [_jsx("ul", {
|
|
352
|
-
children: this.state.items.map(item => _jsx("li", {
|
|
353
|
-
"data-key": item,
|
|
354
|
-
children: item
|
|
355
|
-
}, item))
|
|
356
|
-
}), _jsxs("select", {
|
|
357
|
-
value: this.state.selected,
|
|
358
|
-
children: [_jsx("option", {
|
|
359
|
-
value: "a",
|
|
360
|
-
children: "A"
|
|
361
|
-
}), _jsx("option", {
|
|
362
|
-
value: "b",
|
|
363
|
-
children: "B"
|
|
364
|
-
}), _jsx("option", {
|
|
365
|
-
value: "c",
|
|
366
|
-
children: "C"
|
|
367
|
-
})]
|
|
368
|
-
}), _jsx("button", {
|
|
369
|
-
onClick: () => {
|
|
370
|
-
this.state.updates++;
|
|
371
|
-
},
|
|
372
|
-
children: "update"
|
|
373
|
-
}), _jsx("output", {
|
|
374
|
-
children: this.state.updates
|
|
375
|
-
})]
|
|
376
|
-
}));
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
const testApp = await createTestApp(ReconciliationRoot, {
|
|
380
|
-
document
|
|
381
|
-
});
|
|
382
|
-
const nodeA = testApp.container.querySelector('[data-key="a"]');
|
|
383
|
-
const nodeB = testApp.container.querySelector('[data-key="b"]');
|
|
384
|
-
const select = testApp.container.querySelector('select');
|
|
385
|
-
testApp.component.state.items = ['c', 'a', 'b'];
|
|
386
|
-
await testApp.resolved();
|
|
387
|
-
expect(testApp.container.querySelector('[data-key="a"]')).to.equal(nodeA);
|
|
388
|
-
expect(testApp.container.querySelector('[data-key="b"]')).to.equal(nodeB);
|
|
389
|
-
expect(select.value).to.equal('b');
|
|
390
|
-
testApp.container.querySelector('button').dispatchEvent(new window.Event('click'));
|
|
391
|
-
await testApp.resolved();
|
|
392
|
-
expect(select.value).to.equal('b');
|
|
393
|
-
await testApp.unmount();
|
|
394
|
-
});
|
|
395
|
-
it('rejects resolved() when an asynchronous event handler fails', async () => {
|
|
396
|
-
const window = new Window();
|
|
397
|
-
const document = window.document;
|
|
398
|
-
class BrokenEventRoot extends TungstenSimpleComponent {
|
|
399
|
-
constructor(...args) {
|
|
400
|
-
super(...args);
|
|
401
|
-
_defineProperty(this, "content", () => _jsx("button", {
|
|
402
|
-
onClick: async () => {
|
|
403
|
-
await Promise.resolve();
|
|
404
|
-
throw new Error('event failed');
|
|
405
|
-
},
|
|
406
|
-
children: "break"
|
|
407
|
-
}));
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
const testApp = await createTestApp(BrokenEventRoot, {
|
|
411
|
-
document
|
|
412
|
-
});
|
|
413
|
-
testApp.container.querySelector('button').dispatchEvent(new window.Event('click'));
|
|
414
|
-
let error;
|
|
415
|
-
try {
|
|
416
|
-
await testApp.resolved();
|
|
417
|
-
} catch (caught) {
|
|
418
|
-
error = caught;
|
|
419
|
-
}
|
|
420
|
-
expect(error).to.be.instanceOf(Error);
|
|
421
|
-
expect(error.message).to.contain('BrokenEventRoot click event handler');
|
|
422
|
-
await testApp.unmount();
|
|
423
|
-
});
|
|
424
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ensureTestEnvLoaded.d.ts","sourceRoot":"","sources":["../../src/testing/ensureTestEnvLoaded.ts"],"names":[],"mappings":"AASA,wBAAgB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAWnD"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { getUserConfig } from "../getUserConfig.js";
|
|
2
|
-
import { loadEnvsDev } from "../Servers/loadEnvsDev.js";
|
|
3
|
-
let loadPromise;
|
|
4
|
-
// Populate global.tungstenPublicEnv / global.tungstenPrivateEnv for DOM component
|
|
5
|
-
// tests, exactly once per process. Compiled client component code reads these as
|
|
6
|
-
// bare globals, so they must exist before any component runs under the test runner.
|
|
7
|
-
// Test runs prefer .env.test* files (per-file replacement) via useTestEnvFiles.
|
|
8
|
-
export function ensureTestEnvLoaded() {
|
|
9
|
-
if (loadPromise === undefined) {
|
|
10
|
-
loadPromise = (async () => {
|
|
11
|
-
const config = await getUserConfig('silent');
|
|
12
|
-
// iteration 0: schemas are stable within a test run.
|
|
13
|
-
// _firstLoad = false: suppress the CLI boot output.
|
|
14
|
-
// useTestEnvFiles = true: prefer .env.test* files.
|
|
15
|
-
await loadEnvsDev(config, 0, process.env, false, true);
|
|
16
|
-
})();
|
|
17
|
-
}
|
|
18
|
-
return loadPromise;
|
|
19
|
-
}
|
package/dist/testing/index.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { TungstenBaseComponent, TungstenBaseComponentType } from '../Components.jsx';
|
|
2
|
-
import type { TrueStore } from '../Store/makeStore.js';
|
|
3
|
-
export type ComponentProps<ComponentType extends TungstenBaseComponentType<any, any>> = InstanceType<ComponentType>['props'];
|
|
4
|
-
export type TestStoreRegistration<Schema extends object> = {
|
|
5
|
-
getStore: (stores: TrueStore<unknown>[]) => TrueStore<Schema>;
|
|
6
|
-
};
|
|
7
|
-
export type TestStore<Schema extends object> = TrueStore<Schema>;
|
|
8
|
-
export type TestStoreOverride<Schema extends object = object> = {
|
|
9
|
-
registration: TestStoreRegistration<Schema>;
|
|
10
|
-
initialize: (store: TrueStore<Schema>) => void | Promise<void>;
|
|
11
|
-
};
|
|
12
|
-
export declare function testStore<Schema extends object>(registration: TestStoreRegistration<Schema>, initialize: (store: TestStore<Schema>) => void | Promise<void>): TestStoreOverride<Schema>;
|
|
13
|
-
export type TestAppOptions<Props extends object> = {
|
|
14
|
-
document?: Document;
|
|
15
|
-
props?: Props;
|
|
16
|
-
container?: HTMLElement;
|
|
17
|
-
location?: string;
|
|
18
|
-
routeParams?: Record<string, string>;
|
|
19
|
-
routeData?: unknown;
|
|
20
|
-
globalData?: Record<string, unknown>;
|
|
21
|
-
stores?: readonly TestStoreOverride<any>[];
|
|
22
|
-
};
|
|
23
|
-
export type TestApp<Component extends TungstenBaseComponent> = {
|
|
24
|
-
readonly document: Document;
|
|
25
|
-
readonly container: HTMLElement;
|
|
26
|
-
readonly component: Component;
|
|
27
|
-
resolved(): Promise<void>;
|
|
28
|
-
setProps(props: Component['props']): Promise<void>;
|
|
29
|
-
unmount(): Promise<void>;
|
|
30
|
-
};
|
|
31
|
-
type CreateTestAppArgs<Props extends object> = object extends Props ? [options?: TestAppOptions<Props>] : keyof Props extends never ? [options?: TestAppOptions<Props>] : [options: TestAppOptions<Props> & {
|
|
32
|
-
props: Props;
|
|
33
|
-
}];
|
|
34
|
-
export declare function createTestApp<ComponentType extends TungstenBaseComponentType<any, any>>(RootComponent: ComponentType, ...args: CreateTestAppArgs<ComponentProps<ComponentType>>): Promise<TestApp<InstanceType<ComponentType>>>;
|
|
35
|
-
export {};
|
|
36
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,qBAAqB,EACrB,yBAAyB,EAC5B,MAAM,mBAAmB,CAAA;AAO1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAWtD,MAAM,MAAM,cAAc,CACtB,aAAa,SAAS,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,IACzD,YAAY,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,CAAA;AAExC,MAAM,MAAM,qBAAqB,CAAC,MAAM,SAAS,MAAM,IAAI;IACvD,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,SAAS,CAAC,MAAM,CAAC,CAAA;CAChE,CAAA;AAED,MAAM,MAAM,SAAS,CAAC,MAAM,SAAS,MAAM,IAAI,SAAS,CAAC,MAAM,CAAC,CAAA;AAEhE,MAAM,MAAM,iBAAiB,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,IAAI;IAC5D,YAAY,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAA;IAC3C,UAAU,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjE,CAAA;AAED,wBAAgB,SAAS,CAAC,MAAM,SAAS,MAAM,EAC3C,YAAY,EAAE,qBAAqB,CAAC,MAAM,CAAC,EAC3C,UAAU,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC/D,iBAAiB,CAAC,MAAM,CAAC,CAE3B;AAED,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,MAAM,IAAI;IAC/C,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,SAAS,CAAC,EAAE,WAAW,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,MAAM,CAAC,EAAE,SAAS,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAA;CAC7C,CAAA;AAED,MAAM,MAAM,OAAO,CAAC,SAAS,SAAS,qBAAqB,IAAI;IAC3D,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAE7B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B,CAAA;AAED,KAAK,iBAAiB,CAAC,KAAK,SAAS,MAAM,IAAI,MAAM,SAAS,KAAK,GAC7D,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,GACjC,MAAM,KAAK,SAAS,KAAK,GACrB,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,GACjC,CAAC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAqE7D,wBAAsB,aAAa,CAC/B,aAAa,SAAS,yBAAyB,CAAC,GAAG,EAAE,GAAG,CAAC,EAEzD,aAAa,EAAE,aAAa,EAC5B,GAAG,IAAI,EAAE,iBAAiB,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,GAC1D,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,CAoH/C"}
|
package/dist/testing/index.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
|
|
2
|
-
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
|
|
3
|
-
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
4
|
-
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
5
|
-
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
6
|
-
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
|
|
7
|
-
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
|
|
8
|
-
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
|
|
9
|
-
import { Instance } from "../Instance/Instance.js";
|
|
10
|
-
import { initStores } from "../Servers/initStores.js";
|
|
11
|
-
import { ensureTestEnvLoaded } from "./ensureTestEnvLoaded.js";
|
|
12
|
-
import { createTungstenRootComponent, deleteTungstenRoot } from "../update/integrateUpdateVdom.js";
|
|
13
|
-
import { UpdateScheduler } from "../update/UpdateScheduler.js";
|
|
14
|
-
export function testStore(registration, initialize) {
|
|
15
|
-
return {
|
|
16
|
-
registration,
|
|
17
|
-
initialize
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
var _instance = /*#__PURE__*/new WeakMap();
|
|
21
|
-
var _scheduler = /*#__PURE__*/new WeakMap();
|
|
22
|
-
var _root = /*#__PURE__*/new WeakMap();
|
|
23
|
-
var _ownsContainer = /*#__PURE__*/new WeakMap();
|
|
24
|
-
var _unmounted = /*#__PURE__*/new WeakMap();
|
|
25
|
-
class TestAppImplementation {
|
|
26
|
-
constructor(document, container, component, instance, scheduler, root, ownsContainer) {
|
|
27
|
-
_defineProperty(this, "document", void 0);
|
|
28
|
-
_defineProperty(this, "container", void 0);
|
|
29
|
-
_defineProperty(this, "component", void 0);
|
|
30
|
-
_classPrivateFieldInitSpec(this, _instance, void 0);
|
|
31
|
-
_classPrivateFieldInitSpec(this, _scheduler, void 0);
|
|
32
|
-
_classPrivateFieldInitSpec(this, _root, void 0);
|
|
33
|
-
_classPrivateFieldInitSpec(this, _ownsContainer, void 0);
|
|
34
|
-
_classPrivateFieldInitSpec(this, _unmounted, false);
|
|
35
|
-
_defineProperty(this, "resolved", async () => {
|
|
36
|
-
await _classPrivateFieldGet(_scheduler, this).resolved();
|
|
37
|
-
});
|
|
38
|
-
_defineProperty(this, "setProps", async props => {
|
|
39
|
-
if (_classPrivateFieldGet(_unmounted, this)) throw new Error('Cannot set props on an unmounted Tungsten TestApp.');
|
|
40
|
-
this.component.props = props;
|
|
41
|
-
_classPrivateFieldGet(_instance, this).queueUpdate(this.component);
|
|
42
|
-
await this.resolved();
|
|
43
|
-
});
|
|
44
|
-
_defineProperty(this, "unmount", async () => {
|
|
45
|
-
if (_classPrivateFieldGet(_unmounted, this)) return;
|
|
46
|
-
_classPrivateFieldSet(_unmounted, this, true);
|
|
47
|
-
_classPrivateFieldGet(_scheduler, this).cancelPending();
|
|
48
|
-
let cleanupError;
|
|
49
|
-
try {
|
|
50
|
-
deleteTungstenRoot(_classPrivateFieldGet(_root, this));
|
|
51
|
-
_classPrivateFieldGet(_instance, this).disposeRegisteredComponents();
|
|
52
|
-
await _classPrivateFieldGet(_scheduler, this).resolved();
|
|
53
|
-
} catch (error) {
|
|
54
|
-
cleanupError = error;
|
|
55
|
-
try {
|
|
56
|
-
_classPrivateFieldGet(_instance, this).disposeRegisteredComponents();
|
|
57
|
-
} catch {
|
|
58
|
-
// Preserve the first cleanup failure.
|
|
59
|
-
}
|
|
60
|
-
} finally {
|
|
61
|
-
_classPrivateFieldGet(_scheduler, this).dispose();
|
|
62
|
-
if (_classPrivateFieldGet(_ownsContainer, this)) this.container.remove();else while (this.container.firstChild !== null) this.container.removeChild(this.container.firstChild);
|
|
63
|
-
}
|
|
64
|
-
if (cleanupError !== undefined) throw cleanupError;
|
|
65
|
-
});
|
|
66
|
-
this.document = document;
|
|
67
|
-
this.container = container;
|
|
68
|
-
this.component = component;
|
|
69
|
-
_classPrivateFieldSet(_instance, this, instance);
|
|
70
|
-
_classPrivateFieldSet(_scheduler, this, scheduler);
|
|
71
|
-
_classPrivateFieldSet(_root, this, root);
|
|
72
|
-
_classPrivateFieldSet(_ownsContainer, this, ownsContainer);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
export async function createTestApp(RootComponent, ...args) {
|
|
76
|
-
await ensureTestEnvLoaded();
|
|
77
|
-
const options = args[0];
|
|
78
|
-
const suppliedDocument = options?.document ?? globalThis.document;
|
|
79
|
-
if (suppliedDocument === undefined) {
|
|
80
|
-
// eslint-disable-line @typescript-eslint/no-unnecessary-condition
|
|
81
|
-
throw new Error('createTestApp() requires options.document when no global document is available.');
|
|
82
|
-
}
|
|
83
|
-
const document = suppliedDocument;
|
|
84
|
-
const ownsContainer = options?.container === undefined;
|
|
85
|
-
const container = options?.container ?? document.createElement('div');
|
|
86
|
-
if (container.ownerDocument !== document) {
|
|
87
|
-
throw new Error('The TestApp container must belong to options.document.');
|
|
88
|
-
}
|
|
89
|
-
if (container.childNodes.length > 0) {
|
|
90
|
-
throw new Error('The TestApp container must be empty before mounting.');
|
|
91
|
-
}
|
|
92
|
-
if (ownsContainer) {
|
|
93
|
-
if (document.body === null) {
|
|
94
|
-
// eslint-disable-line @typescript-eslint/no-unnecessary-condition
|
|
95
|
-
throw new Error('The supplied document must have a body element.');
|
|
96
|
-
}
|
|
97
|
-
document.body.appendChild(container);
|
|
98
|
-
}
|
|
99
|
-
const location = options?.location ?? '/';
|
|
100
|
-
const globalData = options?.globalData ?? {};
|
|
101
|
-
const matchedRouteInfo = {
|
|
102
|
-
key: null,
|
|
103
|
-
params: options?.routeParams ?? {},
|
|
104
|
-
content: null,
|
|
105
|
-
...(options !== undefined && 'routeData' in options ? {
|
|
106
|
-
data: options.routeData
|
|
107
|
-
} : {})
|
|
108
|
-
};
|
|
109
|
-
const scheduler = new UpdateScheduler();
|
|
110
|
-
const runtimeContext = {
|
|
111
|
-
document,
|
|
112
|
-
window: document.defaultView,
|
|
113
|
-
scheduler,
|
|
114
|
-
navigation: false
|
|
115
|
-
};
|
|
116
|
-
const {
|
|
117
|
-
internalStore,
|
|
118
|
-
stores,
|
|
119
|
-
storeSubscriptions
|
|
120
|
-
} = initStores(location, null, globalData, matchedRouteInfo);
|
|
121
|
-
const instanceConfig = {
|
|
122
|
-
prod: false,
|
|
123
|
-
useRootComponent: true,
|
|
124
|
-
clientSideNav: false,
|
|
125
|
-
siteBasePath: '/',
|
|
126
|
-
staticSite: false
|
|
127
|
-
};
|
|
128
|
-
const emptyRoutes = {};
|
|
129
|
-
const instance = new Instance(location, instanceConfig, null, emptyRoutes, emptyRoutes, internalStore, stores, storeSubscriptions, globalData, matchedRouteInfo, runtimeContext);
|
|
130
|
-
try {
|
|
131
|
-
for (const override of options?.stores ?? []) {
|
|
132
|
-
const store = override.registration.getStore(instance.stores);
|
|
133
|
-
await override.initialize(store);
|
|
134
|
-
}
|
|
135
|
-
const props = options?.props ?? {};
|
|
136
|
-
const root = await createTungstenRootComponent(RootComponent, props, options?.routeParams, options?.routeData, instance, container);
|
|
137
|
-
const testApp = new TestAppImplementation(document, container, root.component, instance, scheduler, root, ownsContainer);
|
|
138
|
-
try {
|
|
139
|
-
await testApp.resolved();
|
|
140
|
-
return testApp;
|
|
141
|
-
} catch (error) {
|
|
142
|
-
await testApp.unmount();
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
145
|
-
} catch (error) {
|
|
146
|
-
try {
|
|
147
|
-
instance.disposeRegisteredComponents();
|
|
148
|
-
} catch {
|
|
149
|
-
// Preserve the mount failure.
|
|
150
|
-
}
|
|
151
|
-
scheduler.dispose();
|
|
152
|
-
while (container.firstChild !== null) container.removeChild(container.firstChild);
|
|
153
|
-
if (ownsContainer) container.remove();
|
|
154
|
-
throw error;
|
|
155
|
-
}
|
|
156
|
-
}
|