forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtransfer_state_spec.ts
153 lines (123 loc) · 5.17 KB
/
transfer_state_spec.ts
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
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {APP_ID as APP_ID_TOKEN, PLATFORM_ID} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {getDocument} from '../src/render3/interfaces/document';
import {makeStateKey, TransferState} from '../src/transfer_state';
function removeScriptTag(doc: Document, id: string) {
doc.getElementById(id)?.remove();
}
function addScriptTag(doc: Document, appId: string, data: object | string) {
const script = doc.createElement('script');
const id = appId + '-state';
script.id = id;
script.setAttribute('type', 'application/json');
script.textContent = typeof data === 'string' ? data : JSON.stringify(data);
// Remove any stale script tags.
removeScriptTag(doc, id);
doc.body.appendChild(script);
}
describe('TransferState', () => {
const APP_ID = 'test-app';
let doc: Document;
const TEST_KEY = makeStateKey<number>('test');
const BOOLEAN_KEY = makeStateKey<boolean>('boolean');
const DELAYED_KEY = makeStateKey<string>('delayed');
beforeEach(() => {
doc = getDocument();
TestBed.configureTestingModule({
providers: [
{provide: APP_ID_TOKEN, useValue: APP_ID},
{provide: PLATFORM_ID, useValue: 'browser'},
],
});
});
afterEach(() => {
removeScriptTag(doc, APP_ID + '-state');
});
it('is initialized from script tag', () => {
addScriptTag(doc, APP_ID, {test: 10});
const transferState: TransferState = TestBed.inject(TransferState);
expect(transferState.get(TEST_KEY, 0)).toBe(10);
});
it('is initialized to empty state if script tag not found', () => {
const transferState: TransferState = TestBed.inject(TransferState);
expect(transferState.get(TEST_KEY, 0)).toBe(0);
});
it('supports adding new keys using set', () => {
const transferState: TransferState = TestBed.inject(TransferState);
transferState.set(TEST_KEY, 20);
expect(transferState.get(TEST_KEY, 0)).toBe(20);
expect(transferState.hasKey(TEST_KEY)).toBe(true);
});
it("supports setting and accessing value '0' via get", () => {
const transferState: TransferState = TestBed.inject(TransferState);
transferState.set(TEST_KEY, 0);
expect(transferState.get(TEST_KEY, 20)).toBe(0);
expect(transferState.hasKey(TEST_KEY)).toBe(true);
});
it("supports setting and accessing value 'false' via get", () => {
const transferState: TransferState = TestBed.inject(TransferState);
transferState.set(BOOLEAN_KEY, false);
expect(transferState.get(BOOLEAN_KEY, true)).toBe(false);
expect(transferState.hasKey(BOOLEAN_KEY)).toBe(true);
});
it("supports setting and accessing value 'null' via get", () => {
const transferState: TransferState = TestBed.inject(TransferState);
transferState.set(TEST_KEY, null);
expect(transferState.get(TEST_KEY, 20 as any)).toBe(null);
expect(transferState.hasKey(TEST_KEY)).toBe(true);
});
it('supports removing keys', () => {
const transferState: TransferState = TestBed.inject(TransferState);
transferState.set(TEST_KEY, 20);
transferState.remove(TEST_KEY);
expect(transferState.get(TEST_KEY, 0)).toBe(0);
expect(transferState.hasKey(TEST_KEY)).toBe(false);
});
it('supports serialization using toJson()', () => {
const transferState: TransferState = TestBed.inject(TransferState);
transferState.set(TEST_KEY, 20);
expect(transferState.toJson()).toBe('{"test":20}');
});
it('calls onSerialize callbacks when calling toJson()', () => {
const transferState: TransferState = TestBed.inject(TransferState);
transferState.set(TEST_KEY, 20);
let value = 'initial';
transferState.onSerialize(DELAYED_KEY, () => value);
value = 'changed';
expect(transferState.toJson()).toBe('{"test":20,"delayed":"changed"}');
});
it('should provide an ability to detect whether the state is empty', () => {
const transferState = TestBed.inject(TransferState);
// The state is empty initially.
expect(transferState.isEmpty).toBeTrue();
transferState.set(TEST_KEY, 20);
expect(transferState.isEmpty).toBeFalse();
transferState.remove(TEST_KEY);
expect(transferState.isEmpty).toBeTrue();
});
it('should encode `<` to avoid breaking out of <script> tag in serialized output', () => {
const transferState = TestBed.inject(TransferState);
// The state is empty initially.
expect(transferState.isEmpty).toBeTrue();
transferState.set(DELAYED_KEY, '</script><script>alert(\'Hello&\' + "World");');
expect(transferState.toJson()).toBe(
`{"delayed":"\\u003C/script>\\u003Cscript>alert('Hello&' + \\"World\\");"}`,
);
});
it('should decode `\\u003C` (<) when restoring stating', () => {
const encodedState = `{"delayed":"\\u003C/script>\\u003Cscript>alert('Hello&' + \\"World\\");"}`;
addScriptTag(doc, APP_ID, encodedState);
const transferState = TestBed.inject(TransferState);
expect(transferState.toJson()).toBe(encodedState);
expect(transferState.get(DELAYED_KEY, null)).toBe(
'</script><script>alert(\'Hello&\' + "World");',
);
});
});