slicejs-web-framework 3.3.7 → 3.3.8
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/.opencode/opencode.json +14 -13
- package/Slice/Components/Structural/ContextManager/ContextManager.js +420 -423
- package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +421 -420
- package/Slice/Components/Structural/Controller/Controller.js +1198 -1199
- package/Slice/Components/Structural/Debugger/Debugger.js +1504 -1497
- package/Slice/Components/Structural/EventManager/EventManager.js +334 -338
- package/Slice/Components/Structural/Logger/Logger.js +200 -145
- package/Slice/Components/Structural/Router/Router.js +775 -760
- package/Slice/Components/Structural/StylesManager/StylesManager.js +82 -82
- package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +106 -106
- package/Slice/Slice.js +618 -619
- package/Slice/tests/build-singleton.test.js +244 -244
- package/Slice/tests/bundle-v2-runtime-contract.test.js +738 -728
- package/Slice/tests/context-patch-use.test.js +95 -95
- package/Slice/tests/fixtures/real-runtime-loader.mjs +21 -21
- package/Slice/tests/public-env-typed-accessors.test.js +66 -66
- package/api/index.js +281 -286
- package/api/utils/publicEnvResolver.js +123 -117
- package/package.json +44 -44
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
|
|
4
|
-
globalThis.alert = () => {};
|
|
5
|
-
|
|
6
|
-
// Minimal global `slice` so ContextManager's slice.logger / slice.events work in
|
|
7
|
-
// isolation (a tiny in-memory event bus drives the watch notifications).
|
|
8
|
-
const subscribers = new Map(); // eventName -> Set<fn>
|
|
9
|
-
let subId = 0;
|
|
10
|
-
globalThis.slice = {
|
|
11
|
-
logger: { logError() {}, logInfo() {}, logWarning() {} },
|
|
12
|
-
events: {
|
|
13
|
-
subscribe(name, cb) {
|
|
14
|
-
if (!subscribers.has(name)) subscribers.set(name, new Set());
|
|
15
|
-
subscribers.get(name).add(cb);
|
|
16
|
-
return `sub-${++subId}`;
|
|
17
|
-
},
|
|
18
|
-
emit(name, ...args) {
|
|
19
|
-
for (const cb of subscribers.get(name) ?? []) cb(...args);
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const { default: ContextManager } = await import(
|
|
25
|
-
'../Components/Structural/ContextManager/ContextManager.js'
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
function freshContext(name, initial) {
|
|
29
|
-
const ctx = new ContextManager();
|
|
30
|
-
ctx.create(name, initial);
|
|
31
|
-
return ctx;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
test('patch merges into existing state (does not replace)', () => {
|
|
35
|
-
const ctx = freshContext('cart', { items: 2, total: 47, discount: 0 });
|
|
36
|
-
ctx.patch('cart', { discount: 0.1 });
|
|
37
|
-
assert.deepEqual(ctx.getState('cart'), { items: 2, total: 47, discount: 0.1 });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('setState replaces, patch keeps the rest', () => {
|
|
41
|
-
const ctx = freshContext('s', { a: 1, b: 2 });
|
|
42
|
-
ctx.setState('s', { a: 9 }); // replace → drops b
|
|
43
|
-
assert.deepEqual(ctx.getState('s'), { a: 9 });
|
|
44
|
-
ctx.patch('s', { b: 5 }); // merge onto { a: 9 }
|
|
45
|
-
assert.deepEqual(ctx.getState('s'), { a: 9, b: 5 });
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('patch rejects non-object partials and missing contexts (no throw)', () => {
|
|
49
|
-
const ctx = freshContext('s', { a: 1 });
|
|
50
|
-
ctx.patch('s', null);
|
|
51
|
-
ctx.patch('s', [1, 2]);
|
|
52
|
-
ctx.patch('nope', { x: 1 });
|
|
53
|
-
assert.deepEqual(ctx.getState('s'), { a: 1 });
|
|
54
|
-
assert.equal(ctx.has('nope'), false);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('use(name) returns a handle bound to that context', () => {
|
|
58
|
-
const ctx = freshContext('settings', { model: 'a' });
|
|
59
|
-
const settings = ctx.use('settings');
|
|
60
|
-
|
|
61
|
-
assert.equal(settings.get().model, 'a');
|
|
62
|
-
settings.set({ model: 'b' });
|
|
63
|
-
assert.equal(ctx.getState('settings').model, 'b');
|
|
64
|
-
settings.patch({ theme: 'dark' });
|
|
65
|
-
assert.deepEqual(ctx.getState('settings'), { model: 'b', theme: 'dark' });
|
|
66
|
-
assert.equal(settings.has(), true);
|
|
67
|
-
settings.destroy();
|
|
68
|
-
assert.equal(ctx.has('settings'), false);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('use(name).bind calls back immediately and on every change', () => {
|
|
72
|
-
const ctx = freshContext('cart', { items: [], n: 0 });
|
|
73
|
-
const calls = [];
|
|
74
|
-
ctx.use('cart').bind({ sliceId: 'c1' }, (state) => calls.push(state.n));
|
|
75
|
-
|
|
76
|
-
assert.deepEqual(calls, [0]); // immediate initial call
|
|
77
|
-
ctx.patch('cart', { n: 1 });
|
|
78
|
-
assert.deepEqual(calls, [0, 1]); // fired on change
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('use(name).bind with a selector only fires when the selected value changes', () => {
|
|
82
|
-
const ctx = freshContext('cart', { items: ['x'], note: 'a' });
|
|
83
|
-
const counts = [];
|
|
84
|
-
ctx.use('cart').bind(
|
|
85
|
-
{ sliceId: 'c2' },
|
|
86
|
-
(count) => counts.push(count),
|
|
87
|
-
(s) => s.items.length
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
assert.deepEqual(counts, [1]); // immediate, selected value = length
|
|
91
|
-
ctx.patch('cart', { note: 'b' }); // length unchanged → no fire
|
|
92
|
-
assert.deepEqual(counts, [1]);
|
|
93
|
-
ctx.patch('cart', { items: ['x', 'y'] }); // length changed → fire
|
|
94
|
-
assert.deepEqual(counts, [1, 2]);
|
|
95
|
-
});
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
globalThis.alert = () => {};
|
|
5
|
+
|
|
6
|
+
// Minimal global `slice` so ContextManager's slice.logger / slice.events work in
|
|
7
|
+
// isolation (a tiny in-memory event bus drives the watch notifications).
|
|
8
|
+
const subscribers = new Map(); // eventName -> Set<fn>
|
|
9
|
+
let subId = 0;
|
|
10
|
+
globalThis.slice = {
|
|
11
|
+
logger: { logError() {}, logInfo() {}, logWarning() {} },
|
|
12
|
+
events: {
|
|
13
|
+
subscribe(name, cb) {
|
|
14
|
+
if (!subscribers.has(name)) subscribers.set(name, new Set());
|
|
15
|
+
subscribers.get(name).add(cb);
|
|
16
|
+
return `sub-${++subId}`;
|
|
17
|
+
},
|
|
18
|
+
emit(name, ...args) {
|
|
19
|
+
for (const cb of subscribers.get(name) ?? []) cb(...args);
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const { default: ContextManager } = await import(
|
|
25
|
+
'../Components/Structural/ContextManager/ContextManager.js'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
function freshContext(name, initial) {
|
|
29
|
+
const ctx = new ContextManager();
|
|
30
|
+
ctx.create(name, initial);
|
|
31
|
+
return ctx;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test('patch merges into existing state (does not replace)', () => {
|
|
35
|
+
const ctx = freshContext('cart', { items: 2, total: 47, discount: 0 });
|
|
36
|
+
ctx.patch('cart', { discount: 0.1 });
|
|
37
|
+
assert.deepEqual(ctx.getState('cart'), { items: 2, total: 47, discount: 0.1 });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('setState replaces, patch keeps the rest', () => {
|
|
41
|
+
const ctx = freshContext('s', { a: 1, b: 2 });
|
|
42
|
+
ctx.setState('s', { a: 9 }); // replace → drops b
|
|
43
|
+
assert.deepEqual(ctx.getState('s'), { a: 9 });
|
|
44
|
+
ctx.patch('s', { b: 5 }); // merge onto { a: 9 }
|
|
45
|
+
assert.deepEqual(ctx.getState('s'), { a: 9, b: 5 });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('patch rejects non-object partials and missing contexts (no throw)', () => {
|
|
49
|
+
const ctx = freshContext('s', { a: 1 });
|
|
50
|
+
ctx.patch('s', null);
|
|
51
|
+
ctx.patch('s', [1, 2]);
|
|
52
|
+
ctx.patch('nope', { x: 1 });
|
|
53
|
+
assert.deepEqual(ctx.getState('s'), { a: 1 });
|
|
54
|
+
assert.equal(ctx.has('nope'), false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('use(name) returns a handle bound to that context', () => {
|
|
58
|
+
const ctx = freshContext('settings', { model: 'a' });
|
|
59
|
+
const settings = ctx.use('settings');
|
|
60
|
+
|
|
61
|
+
assert.equal(settings.get().model, 'a');
|
|
62
|
+
settings.set({ model: 'b' });
|
|
63
|
+
assert.equal(ctx.getState('settings').model, 'b');
|
|
64
|
+
settings.patch({ theme: 'dark' });
|
|
65
|
+
assert.deepEqual(ctx.getState('settings'), { model: 'b', theme: 'dark' });
|
|
66
|
+
assert.equal(settings.has(), true);
|
|
67
|
+
settings.destroy();
|
|
68
|
+
assert.equal(ctx.has('settings'), false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('use(name).bind calls back immediately and on every change', () => {
|
|
72
|
+
const ctx = freshContext('cart', { items: [], n: 0 });
|
|
73
|
+
const calls = [];
|
|
74
|
+
ctx.use('cart').bind({ sliceId: 'c1' }, (state) => calls.push(state.n));
|
|
75
|
+
|
|
76
|
+
assert.deepEqual(calls, [0]); // immediate initial call
|
|
77
|
+
ctx.patch('cart', { n: 1 });
|
|
78
|
+
assert.deepEqual(calls, [0, 1]); // fired on change
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('use(name).bind with a selector only fires when the selected value changes', () => {
|
|
82
|
+
const ctx = freshContext('cart', { items: ['x'], note: 'a' });
|
|
83
|
+
const counts = [];
|
|
84
|
+
ctx.use('cart').bind(
|
|
85
|
+
{ sliceId: 'c2' },
|
|
86
|
+
(count) => counts.push(count),
|
|
87
|
+
(s) => s.items.length
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
assert.deepEqual(counts, [1]); // immediate, selected value = length
|
|
91
|
+
ctx.patch('cart', { note: 'b' }); // length unchanged → no fire
|
|
92
|
+
assert.deepEqual(counts, [1]);
|
|
93
|
+
ctx.patch('cart', { items: ['x', 'y'] }); // length changed → fire
|
|
94
|
+
assert.deepEqual(counts, [1, 2]);
|
|
95
|
+
});
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
// Node ESM resolve hook so the REAL Controller can load under `node --test`.
|
|
2
|
-
//
|
|
3
|
-
// Controller.js does `import components from '/Components/components.js'` — a
|
|
4
|
-
// browser-absolute path that the dev server serves but node can't resolve. We
|
|
5
|
-
// map only that exact specifier to a small fixture components map (just data,
|
|
6
|
-
// the same shape the real components.js exports). Everything else resolves
|
|
7
|
-
// normally, so the tests exercise the real Slice + Controller code, not mocks.
|
|
8
|
-
// AppProbe lives in a CUSTOM category ('AppServices') whose type is 'Service' —
|
|
9
|
-
// it must be allowed as a singleton (the type, not the category name, decides).
|
|
10
|
-
const COMPONENTS = { Probe: 'Service', ModalProbe: 'Visual', AppProbe: 'AppServices' };
|
|
11
|
-
const STUB = `export default ${JSON.stringify(COMPONENTS)};`;
|
|
12
|
-
|
|
13
|
-
export async function resolve(specifier, context, nextResolve) {
|
|
14
|
-
if (specifier === '/Components/components.js') {
|
|
15
|
-
return {
|
|
16
|
-
url: `data:text/javascript,${encodeURIComponent(STUB)}`,
|
|
17
|
-
shortCircuit: true
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
return nextResolve(specifier, context);
|
|
21
|
-
}
|
|
1
|
+
// Node ESM resolve hook so the REAL Controller can load under `node --test`.
|
|
2
|
+
//
|
|
3
|
+
// Controller.js does `import components from '/Components/components.js'` — a
|
|
4
|
+
// browser-absolute path that the dev server serves but node can't resolve. We
|
|
5
|
+
// map only that exact specifier to a small fixture components map (just data,
|
|
6
|
+
// the same shape the real components.js exports). Everything else resolves
|
|
7
|
+
// normally, so the tests exercise the real Slice + Controller code, not mocks.
|
|
8
|
+
// AppProbe lives in a CUSTOM category ('AppServices') whose type is 'Service' —
|
|
9
|
+
// it must be allowed as a singleton (the type, not the category name, decides).
|
|
10
|
+
const COMPONENTS = { Probe: 'Service', ModalProbe: 'Visual', AppProbe: 'AppServices' };
|
|
11
|
+
const STUB = `export default ${JSON.stringify(COMPONENTS)};`;
|
|
12
|
+
|
|
13
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
14
|
+
if (specifier === '/Components/components.js') {
|
|
15
|
+
return {
|
|
16
|
+
url: `data:text/javascript,${encodeURIComponent(STUB)}`,
|
|
17
|
+
shortCircuit: true
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return nextResolve(specifier, context);
|
|
21
|
+
}
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
|
|
4
|
-
globalThis.alert = () => {};
|
|
5
|
-
|
|
6
|
-
const { default: Slice } = await import('../Slice.js');
|
|
7
|
-
|
|
8
|
-
function createSliceInstance() {
|
|
9
|
-
return new Slice({
|
|
10
|
-
paths: {},
|
|
11
|
-
themeManager: {},
|
|
12
|
-
stylesManager: {},
|
|
13
|
-
logger: {},
|
|
14
|
-
debugger: {},
|
|
15
|
-
loading: {},
|
|
16
|
-
events: {}
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
test('slice.env.get / has / all', () => {
|
|
21
|
-
const s = createSliceInstance();
|
|
22
|
-
s.setPublicEnv({ SLICE_PUBLIC_API_URL: 'https://api.example.com', INTERNAL: 'hidden' });
|
|
23
|
-
|
|
24
|
-
assert.equal(s.env.get('SLICE_PUBLIC_API_URL'), 'https://api.example.com');
|
|
25
|
-
assert.equal(s.env.get('SLICE_PUBLIC_MISSING', 'fb'), 'fb');
|
|
26
|
-
assert.equal(s.env.has('SLICE_PUBLIC_API_URL'), true);
|
|
27
|
-
assert.equal(s.env.has('SLICE_PUBLIC_MISSING'), false);
|
|
28
|
-
assert.deepEqual(s.env.all(), { SLICE_PUBLIC_API_URL: 'https://api.example.com' }); // only SLICE_PUBLIC_*
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('slice.env.bool parses truthy strings and falls back', () => {
|
|
32
|
-
const s = createSliceInstance();
|
|
33
|
-
s.setPublicEnv({
|
|
34
|
-
SLICE_PUBLIC_ON: 'true',
|
|
35
|
-
SLICE_PUBLIC_ON2: 'YES',
|
|
36
|
-
SLICE_PUBLIC_ON3: '1',
|
|
37
|
-
SLICE_PUBLIC_OFF: 'false',
|
|
38
|
-
SLICE_PUBLIC_EMPTY: ''
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
assert.equal(s.env.bool('SLICE_PUBLIC_ON'), true);
|
|
42
|
-
assert.equal(s.env.bool('SLICE_PUBLIC_ON2'), true);
|
|
43
|
-
assert.equal(s.env.bool('SLICE_PUBLIC_ON3'), true);
|
|
44
|
-
assert.equal(s.env.bool('SLICE_PUBLIC_OFF'), false);
|
|
45
|
-
assert.equal(s.env.bool('SLICE_PUBLIC_EMPTY', true), true); // empty → fallback
|
|
46
|
-
assert.equal(s.env.bool('SLICE_PUBLIC_MISSING'), false); // missing → default false
|
|
47
|
-
assert.equal(s.env.bool('SLICE_PUBLIC_MISSING', true), true); // missing → custom fallback
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('slice.env.int parses integers and falls back', () => {
|
|
51
|
-
const s = createSliceInstance();
|
|
52
|
-
s.setPublicEnv({ SLICE_PUBLIC_TIMEOUT: '5000', SLICE_PUBLIC_BAD: 'nope' });
|
|
53
|
-
|
|
54
|
-
assert.equal(s.env.int('SLICE_PUBLIC_TIMEOUT'), 5000);
|
|
55
|
-
assert.equal(s.env.int('SLICE_PUBLIC_BAD', 10), 10); // non-numeric → fallback
|
|
56
|
-
assert.equal(s.env.int('SLICE_PUBLIC_MISSING', 42), 42); // missing → fallback
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('slice.env.list splits CSV, trims, drops empties', () => {
|
|
60
|
-
const s = createSliceInstance();
|
|
61
|
-
s.setPublicEnv({ SLICE_PUBLIC_MODELS: ' a , b ,, c ', SLICE_PUBLIC_EMPTY: '' });
|
|
62
|
-
|
|
63
|
-
assert.deepEqual(s.env.list('SLICE_PUBLIC_MODELS'), ['a', 'b', 'c']);
|
|
64
|
-
assert.deepEqual(s.env.list('SLICE_PUBLIC_EMPTY', ['x']), ['x']); // empty → fallback
|
|
65
|
-
assert.deepEqual(s.env.list('SLICE_PUBLIC_MISSING'), []); // missing → default []
|
|
66
|
-
});
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
globalThis.alert = () => {};
|
|
5
|
+
|
|
6
|
+
const { default: Slice } = await import('../Slice.js');
|
|
7
|
+
|
|
8
|
+
function createSliceInstance() {
|
|
9
|
+
return new Slice({
|
|
10
|
+
paths: {},
|
|
11
|
+
themeManager: {},
|
|
12
|
+
stylesManager: {},
|
|
13
|
+
logger: {},
|
|
14
|
+
debugger: {},
|
|
15
|
+
loading: {},
|
|
16
|
+
events: {}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test('slice.env.get / has / all', () => {
|
|
21
|
+
const s = createSliceInstance();
|
|
22
|
+
s.setPublicEnv({ SLICE_PUBLIC_API_URL: 'https://api.example.com', INTERNAL: 'hidden' });
|
|
23
|
+
|
|
24
|
+
assert.equal(s.env.get('SLICE_PUBLIC_API_URL'), 'https://api.example.com');
|
|
25
|
+
assert.equal(s.env.get('SLICE_PUBLIC_MISSING', 'fb'), 'fb');
|
|
26
|
+
assert.equal(s.env.has('SLICE_PUBLIC_API_URL'), true);
|
|
27
|
+
assert.equal(s.env.has('SLICE_PUBLIC_MISSING'), false);
|
|
28
|
+
assert.deepEqual(s.env.all(), { SLICE_PUBLIC_API_URL: 'https://api.example.com' }); // only SLICE_PUBLIC_*
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('slice.env.bool parses truthy strings and falls back', () => {
|
|
32
|
+
const s = createSliceInstance();
|
|
33
|
+
s.setPublicEnv({
|
|
34
|
+
SLICE_PUBLIC_ON: 'true',
|
|
35
|
+
SLICE_PUBLIC_ON2: 'YES',
|
|
36
|
+
SLICE_PUBLIC_ON3: '1',
|
|
37
|
+
SLICE_PUBLIC_OFF: 'false',
|
|
38
|
+
SLICE_PUBLIC_EMPTY: ''
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
assert.equal(s.env.bool('SLICE_PUBLIC_ON'), true);
|
|
42
|
+
assert.equal(s.env.bool('SLICE_PUBLIC_ON2'), true);
|
|
43
|
+
assert.equal(s.env.bool('SLICE_PUBLIC_ON3'), true);
|
|
44
|
+
assert.equal(s.env.bool('SLICE_PUBLIC_OFF'), false);
|
|
45
|
+
assert.equal(s.env.bool('SLICE_PUBLIC_EMPTY', true), true); // empty → fallback
|
|
46
|
+
assert.equal(s.env.bool('SLICE_PUBLIC_MISSING'), false); // missing → default false
|
|
47
|
+
assert.equal(s.env.bool('SLICE_PUBLIC_MISSING', true), true); // missing → custom fallback
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('slice.env.int parses integers and falls back', () => {
|
|
51
|
+
const s = createSliceInstance();
|
|
52
|
+
s.setPublicEnv({ SLICE_PUBLIC_TIMEOUT: '5000', SLICE_PUBLIC_BAD: 'nope' });
|
|
53
|
+
|
|
54
|
+
assert.equal(s.env.int('SLICE_PUBLIC_TIMEOUT'), 5000);
|
|
55
|
+
assert.equal(s.env.int('SLICE_PUBLIC_BAD', 10), 10); // non-numeric → fallback
|
|
56
|
+
assert.equal(s.env.int('SLICE_PUBLIC_MISSING', 42), 42); // missing → fallback
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('slice.env.list splits CSV, trims, drops empties', () => {
|
|
60
|
+
const s = createSliceInstance();
|
|
61
|
+
s.setPublicEnv({ SLICE_PUBLIC_MODELS: ' a , b ,, c ', SLICE_PUBLIC_EMPTY: '' });
|
|
62
|
+
|
|
63
|
+
assert.deepEqual(s.env.list('SLICE_PUBLIC_MODELS'), ['a', 'b', 'c']);
|
|
64
|
+
assert.deepEqual(s.env.list('SLICE_PUBLIC_EMPTY', ['x']), ['x']); // empty → fallback
|
|
65
|
+
assert.deepEqual(s.env.list('SLICE_PUBLIC_MISSING'), []); // missing → default []
|
|
66
|
+
});
|