@mantou/gem 2.2.15 → 2.3.1
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/gem.js +7 -7
- package/dist/gem.mjs +7 -7
- package/elements/base/dialog.d.ts.map +1 -1
- package/elements/base/link.d.ts.map +1 -1
- package/elements/base/link.js +1 -1
- package/elements/base/link.js.map +1 -1
- package/elements/base/modal-factory.d.ts +20 -510
- package/elements/base/modal-factory.d.ts.map +1 -1
- package/elements/base/modal-factory.js +3 -2
- package/elements/base/modal-factory.js.map +1 -1
- package/elements/base/reflect.d.ts +7 -4
- package/elements/base/reflect.d.ts.map +1 -1
- package/elements/base/reflect.js +30 -4
- package/elements/base/reflect.js.map +1 -1
- package/elements/base/route.d.ts +11 -9
- package/elements/base/route.d.ts.map +1 -1
- package/elements/base/route.js +84 -44
- package/elements/base/route.js.map +1 -1
- package/elements/base/title.d.ts.map +1 -1
- package/elements/base/title.js +1 -1
- package/elements/base/title.js.map +1 -1
- package/helper/hmr.js.map +1 -1
- package/helper/i18n.d.ts +21 -17
- package/helper/i18n.d.ts.map +1 -1
- package/helper/i18n.js +172 -116
- package/helper/i18n.js.map +1 -1
- package/helper/logger.d.ts.map +1 -1
- package/helper/mediaquery.d.ts.map +1 -1
- package/helper/request.d.ts +7 -1
- package/helper/request.d.ts.map +1 -1
- package/helper/request.js.map +1 -1
- package/helper/theme.d.ts +8 -4
- package/helper/theme.d.ts.map +1 -1
- package/helper/theme.js +12 -9
- package/helper/theme.js.map +1 -1
- package/lib/decorators.d.ts +3 -2
- package/lib/decorators.d.ts.map +1 -1
- package/lib/decorators.js +29 -44
- package/lib/decorators.js.map +1 -1
- package/lib/directive.d.ts.map +1 -1
- package/lib/history.d.ts +46 -42
- package/lib/history.d.ts.map +1 -1
- package/lib/history.js +19 -37
- package/lib/history.js.map +1 -1
- package/lib/lit-html.d.ts.map +1 -1
- package/lib/lit-html.js.map +1 -1
- package/lib/reactive.d.ts +7 -9
- package/lib/reactive.d.ts.map +1 -1
- package/lib/reactive.js +86 -115
- package/lib/reactive.js.map +1 -1
- package/lib/repeat.d.ts +1 -1
- package/lib/repeat.d.ts.map +1 -1
- package/lib/repeat.js.map +1 -1
- package/lib/server.d.ts +12 -0
- package/lib/server.d.ts.map +1 -1
- package/lib/server.js +2 -0
- package/lib/server.js.map +1 -1
- package/lib/store.d.ts +0 -6
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +5 -17
- package/lib/store.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.d.ts.map +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/package.json +7 -8
- package/src/elements/base/modal-factory.ts +22 -6
- package/src/elements/base/reflect.ts +30 -4
- package/src/elements/base/route.ts +80 -50
- package/src/helper/i18n.ts +184 -127
- package/src/helper/theme.ts +16 -9
- package/src/lib/decorators.ts +36 -49
- package/src/lib/history.ts +19 -32
- package/src/lib/reactive.ts +90 -126
- package/src/lib/server.ts +14 -0
- package/src/lib/store.ts +3 -19
- package/src/lib/version.ts +1 -1
- package/src/test/elements/gesture.test.ts +13 -5
- package/src/test/elements/link.test.ts +12 -2
- package/src/test/elements/reflect.test.ts +20 -1
- package/src/test/elements/route.test.ts +102 -0
- package/src/test/elements/title.test.ts +7 -0
- package/src/test/elements/unsafe.test.ts +22 -0
- package/src/test/gem-element/advance.test.ts +106 -0
- package/src/test/gem-element/basic.test.ts +13 -0
- package/src/test/history.test.ts +19 -0
- package/src/test/theme.test.ts +8 -1
- package/src/test/utils.test.ts +10 -0
- package/src/test/utils.ts +56 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type GemReflectElement, getLogicalParent } from '../../elements/reflect';
|
|
2
2
|
import { adoptedStyle, customElement } from '../../lib/decorators';
|
|
3
3
|
import { css, GemElement, html } from '../../lib/element';
|
|
4
4
|
import { expect, fixture, nextFrame } from '../utils';
|
|
@@ -34,4 +34,23 @@ describe('GemReflectElement', () => {
|
|
|
34
34
|
reflect.remove();
|
|
35
35
|
expect(!!document.querySelector('#one')).to.equal(false);
|
|
36
36
|
});
|
|
37
|
+
|
|
38
|
+
it('logical parent', async () => {
|
|
39
|
+
const reflect: GemReflectElement = await fixture(html`
|
|
40
|
+
<gem-reflect .target=${document.body}><div id="three"><span></span></div></gem-reflect>
|
|
41
|
+
`);
|
|
42
|
+
const div = document.body.querySelector('#three')!;
|
|
43
|
+
expect(getLogicalParent(div)).to.equal(reflect);
|
|
44
|
+
expect(getLogicalParent(div.firstElementChild!)).to.equal(div);
|
|
45
|
+
// 移动后插入片段的节点
|
|
46
|
+
const added = document.createElement('p');
|
|
47
|
+
div.after(added);
|
|
48
|
+
expect(getLogicalParent(added)).to.equal(reflect);
|
|
49
|
+
// 片段外的节点
|
|
50
|
+
expect(getLogicalParent(reflect.parentElement!)).to.equal(document.body);
|
|
51
|
+
const host = document.createElement('div');
|
|
52
|
+
const child = host.attachShadow({ mode: 'open' }).appendChild(document.createElement('p'));
|
|
53
|
+
expect(getLogicalParent(child)).to.equal(host);
|
|
54
|
+
reflect.remove();
|
|
55
|
+
});
|
|
37
56
|
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { GemRouteElement } from '../../elements/route';
|
|
2
|
+
import { html } from '../../lib/element';
|
|
3
|
+
import { history } from '../../lib/history';
|
|
4
|
+
import { expect, fixture, nextFrame, waitForPopState } from '../utils';
|
|
5
|
+
|
|
6
|
+
import '../../elements/route';
|
|
7
|
+
|
|
8
|
+
const routes = [{ pattern: '*', content: html`<div style="height: 1000px"></div>` }];
|
|
9
|
+
|
|
10
|
+
// 滚动位置在路由渲染后更新,恢复位置可能晚一个微任务,等待 `routechange` 后已经更新
|
|
11
|
+
const rendered = (route: GemRouteElement) =>
|
|
12
|
+
new Promise((resolve) => route.addEventListener('routechange', resolve, { once: true }));
|
|
13
|
+
|
|
14
|
+
let container: HTMLElement;
|
|
15
|
+
|
|
16
|
+
async function createRoute() {
|
|
17
|
+
container = await fixture(
|
|
18
|
+
html`<div style="height: 100px; overflow: auto"><gem-route .routes=${routes}></gem-route></div>`,
|
|
19
|
+
);
|
|
20
|
+
return container.querySelector<GemRouteElement>('gem-route')!;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function navigate(route: GemRouteElement, action: () => void) {
|
|
24
|
+
const promise = rendered(route);
|
|
25
|
+
action();
|
|
26
|
+
await promise;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('<gem-route> scrollContainer', () => {
|
|
30
|
+
// 所有路由共享滚动位置,避免之前的路由记录位置
|
|
31
|
+
afterEach(() => container.remove());
|
|
32
|
+
|
|
33
|
+
it('导航时滚动到顶部,后退时恢复位置,同一条历史记录重新渲染时保持位置', async () => {
|
|
34
|
+
const route = await createRoute();
|
|
35
|
+
route.scrollContainer = container;
|
|
36
|
+
await nextFrame();
|
|
37
|
+
await navigate(route, () => history.push({ path: '/scroll-a' }));
|
|
38
|
+
container.scrollTop = 300;
|
|
39
|
+
await navigate(route, () => history.push({ path: '/scroll-b' }));
|
|
40
|
+
expect(container.scrollTop).to.equal(0);
|
|
41
|
+
container.scrollTop = 100;
|
|
42
|
+
await navigate(route, () => (route.key = 1));
|
|
43
|
+
expect(container.scrollTop).to.equal(100);
|
|
44
|
+
await navigate(route, () => waitForPopState(() => history.back()));
|
|
45
|
+
expect(container.scrollTop).to.equal(300);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('前进后退时位置未知则滚动到顶部', async () => {
|
|
49
|
+
const route = await createRoute();
|
|
50
|
+
await navigate(route, () => history.push({ path: '/scroll-c' }));
|
|
51
|
+
await navigate(route, () => history.push({ path: '/scroll-d' }));
|
|
52
|
+
route.scrollContainer = container;
|
|
53
|
+
await nextFrame();
|
|
54
|
+
container.scrollTop = 50;
|
|
55
|
+
await navigate(route, () => waitForPopState(() => history.back()));
|
|
56
|
+
expect(container.scrollTop).to.equal(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('前进后退时位置未知则保持文档的滚动位置', async () => {
|
|
60
|
+
// 避免浏览器恢复滚动位置
|
|
61
|
+
window.history.scrollRestoration = 'manual';
|
|
62
|
+
document.body.style.minHeight = '3000px';
|
|
63
|
+
const route = await createRoute();
|
|
64
|
+
await navigate(route, () => history.push({ path: '/scroll-f' }));
|
|
65
|
+
await navigate(route, () => history.push({ path: '/scroll-g' }));
|
|
66
|
+
route.scrollContainer = document.scrollingElement!;
|
|
67
|
+
await nextFrame();
|
|
68
|
+
scrollTo(0, 50);
|
|
69
|
+
await navigate(route, () => waitForPopState(() => history.back()));
|
|
70
|
+
expect(scrollY).to.equal(50);
|
|
71
|
+
scrollTo(0, 0);
|
|
72
|
+
document.body.style.minHeight = '';
|
|
73
|
+
window.history.scrollRestoration = 'auto';
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('恢复位置前等待 scrollWait', async () => {
|
|
77
|
+
const route = await createRoute();
|
|
78
|
+
route.scrollContainer = container;
|
|
79
|
+
let resolve = () => {};
|
|
80
|
+
const ready = new Promise<void>((r) => (resolve = r));
|
|
81
|
+
route.scrollWait = () => ready;
|
|
82
|
+
await nextFrame();
|
|
83
|
+
await navigate(route, () => history.push({ path: '/scroll-h' }));
|
|
84
|
+
container.scrollTop = 300;
|
|
85
|
+
await navigate(route, () => history.push({ path: '/scroll-i' }));
|
|
86
|
+
expect(container.scrollTop).to.equal(0);
|
|
87
|
+
await navigate(route, () => waitForPopState(() => history.back()));
|
|
88
|
+
expect(container.scrollTop).to.equal(0);
|
|
89
|
+
resolve();
|
|
90
|
+
await ready;
|
|
91
|
+
expect(container.scrollTop).to.equal(300);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('滚动容器为空时不滚动', async () => {
|
|
95
|
+
const route = await createRoute();
|
|
96
|
+
route.scrollContainer = () => null;
|
|
97
|
+
await nextFrame();
|
|
98
|
+
container.scrollTop = 80;
|
|
99
|
+
await navigate(route, () => history.push({ path: '/scroll-e' }));
|
|
100
|
+
expect(container.scrollTop).to.equal(80);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -21,5 +21,12 @@ describe('GemTitleElement', () => {
|
|
|
21
21
|
await nextFrame();
|
|
22
22
|
expect(document.title).to.equal('prefix-Page');
|
|
23
23
|
expect(ele.shadowRoot!.textContent).to.equal('Page');
|
|
24
|
+
|
|
25
|
+
// 只改变 hash 时保持标题,不重复添加前缀
|
|
26
|
+
location.hash = '#a';
|
|
27
|
+
await nextFrame();
|
|
28
|
+
await nextFrame();
|
|
29
|
+
expect(document.title).to.equal('prefix-Page');
|
|
30
|
+
expect(ele.shadowRoot!.textContent).to.equal('Page');
|
|
24
31
|
});
|
|
25
32
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GemUnsafeElement } from '../../elements/unsafe';
|
|
2
|
+
import { css, html } from '../../lib/element';
|
|
3
|
+
import { expect, fixture, nextFrame } from '../utils';
|
|
4
|
+
|
|
5
|
+
import '../../elements/unsafe';
|
|
6
|
+
|
|
7
|
+
describe('GemUnsafeElement', () => {
|
|
8
|
+
it('渲染 HTML 字符串,应用样式', async () => {
|
|
9
|
+
const ele: GemUnsafeElement = await fixture(
|
|
10
|
+
html`<gem-unsafe html="<p>a</p>" .styles=${css`p { color: rgb(255, 0, 0); }`}></gem-unsafe>`,
|
|
11
|
+
);
|
|
12
|
+
const color = () => getComputedStyle(ele.shadowRoot!.querySelector('p')!).color;
|
|
13
|
+
expect(ele.shadowRoot!.querySelector('p')!.textContent).to.equal('a');
|
|
14
|
+
expect(color()).to.equal('rgb(255, 0, 0)');
|
|
15
|
+
ele.styles = 'p { color: rgb(0, 0, 255); }';
|
|
16
|
+
await nextFrame();
|
|
17
|
+
expect(color()).to.equal('rgb(0, 0, 255)');
|
|
18
|
+
ele.html = '<b>b</b>';
|
|
19
|
+
await nextFrame();
|
|
20
|
+
expect(ele.shadowRoot!.innerHTML).to.equal('<b>b</b>');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
attribute,
|
|
14
14
|
connectStore,
|
|
15
15
|
customElement,
|
|
16
|
+
effect,
|
|
16
17
|
emitter,
|
|
17
18
|
mounted,
|
|
18
19
|
property,
|
|
@@ -85,6 +86,92 @@ describe('没有 Shadow DOM 的 gem 元素', () => {
|
|
|
85
86
|
});
|
|
86
87
|
});
|
|
87
88
|
|
|
89
|
+
const scopeStyle = css`
|
|
90
|
+
p {
|
|
91
|
+
text-decoration: underline;
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
@customElement('scope-child-demo')
|
|
96
|
+
class _ScopeChildDemo extends GemElement {
|
|
97
|
+
render() {
|
|
98
|
+
return html`<p>child</p>`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@customElement('scope-gem-demo')
|
|
103
|
+
@adoptedStyle(scopeStyle)
|
|
104
|
+
class ScopeGemDemo extends GemElement {
|
|
105
|
+
render() {
|
|
106
|
+
return html`<p>own</p><article><p>nested</p></article><scope-child-demo></scope-child-demo>`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@customElement('closed-gem-demo')
|
|
111
|
+
@adoptedStyle(css`
|
|
112
|
+
div {
|
|
113
|
+
color: rgb(255, 0, 0);
|
|
114
|
+
}
|
|
115
|
+
`)
|
|
116
|
+
@shadow({ mode: 'closed' })
|
|
117
|
+
class ClosedGemDemo extends GemElement {
|
|
118
|
+
render() {
|
|
119
|
+
return html`<div>closed</div>`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
describe('元素样式的作用域', () => {
|
|
124
|
+
it('Light DOM 的样式不影响有模版的子元素', async () => {
|
|
125
|
+
const el: ScopeGemDemo = await fixture(html`<scope-gem-demo></scope-gem-demo>`);
|
|
126
|
+
const decoration = (selector: string) => getComputedStyle(el.querySelector(selector)!).textDecorationLine;
|
|
127
|
+
expect(decoration(':scope > p')).to.equal('underline');
|
|
128
|
+
expect(decoration('article > p')).to.equal('underline');
|
|
129
|
+
expect(decoration('scope-child-demo > p')).to.equal('none');
|
|
130
|
+
});
|
|
131
|
+
it('closed Shadow DOM', async () => {
|
|
132
|
+
const el: ClosedGemDemo = await fixture(html`<closed-gem-demo></closed-gem-demo>`);
|
|
133
|
+
expect(el.shadowRoot).to.equal(null);
|
|
134
|
+
const div = el.internals.shadowRoot!.querySelector('div')!;
|
|
135
|
+
expect(getComputedStyle(div).color).to.equal('rgb(255, 0, 0)');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
@customElement('effect-ref-demo')
|
|
140
|
+
class EffectRefDemo extends GemElement {
|
|
141
|
+
#state = createState({ hidden: false });
|
|
142
|
+
ref = createRef<HTMLTextAreaElement>();
|
|
143
|
+
values: (HTMLTextAreaElement | undefined)[] = [];
|
|
144
|
+
|
|
145
|
+
@effect((i) => [i.ref.value])
|
|
146
|
+
#observe = ([ele]: [HTMLTextAreaElement | undefined]) => {
|
|
147
|
+
this.values.push(ele);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
toggle = () => this.#state({ hidden: !this.#state.hidden });
|
|
151
|
+
|
|
152
|
+
render() {
|
|
153
|
+
return this.#state.hidden ? null : html`<textarea ${this.ref}></textarea>`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
describe('effect 依赖 ref', () => {
|
|
158
|
+
it('元素重新创建时执行', async () => {
|
|
159
|
+
const el: EffectRefDemo = await fixture(html`<effect-ref-demo></effect-ref-demo>`);
|
|
160
|
+
const [first] = el.values;
|
|
161
|
+
expect(first).to.be.instanceOf(HTMLTextAreaElement);
|
|
162
|
+
// 元素移除时 ref 不会清空,保留最后的元素
|
|
163
|
+
el.toggle();
|
|
164
|
+
await nextFrame();
|
|
165
|
+
expect(el.ref.value).to.equal(first);
|
|
166
|
+
expect(el.values).to.eql([first]);
|
|
167
|
+
el.toggle();
|
|
168
|
+
await nextFrame();
|
|
169
|
+
expect(el.values.length).to.equal(2);
|
|
170
|
+
expect(el.values[1]).to.be.instanceOf(HTMLTextAreaElement);
|
|
171
|
+
expect(el.values[1]).not.to.equal(first);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
88
175
|
class LifecycleGemElement extends GemElement {
|
|
89
176
|
@attribute appTitle = 'default';
|
|
90
177
|
@attribute appTitle2 = 'default2';
|
|
@@ -106,12 +193,14 @@ class LifecycleGemElement extends GemElement {
|
|
|
106
193
|
}
|
|
107
194
|
renderCount = 0;
|
|
108
195
|
mountedCount = 0;
|
|
196
|
+
unmountedCount = 0;
|
|
109
197
|
|
|
110
198
|
@mounted()
|
|
111
199
|
#init = () => {
|
|
112
200
|
this.mountedCount++;
|
|
113
201
|
return () => {
|
|
114
202
|
this.renderCount = 0;
|
|
203
|
+
this.unmountedCount++;
|
|
115
204
|
};
|
|
116
205
|
};
|
|
117
206
|
|
|
@@ -155,10 +244,12 @@ describe('gem element 生命周期', () => {
|
|
|
155
244
|
expect(el2.renderCount).to.equal(1);
|
|
156
245
|
el2.remove();
|
|
157
246
|
expect(el2.mountedCount).to.equal(1);
|
|
247
|
+
expect(el2.unmountedCount).to.equal(1);
|
|
158
248
|
expect(el2.renderCount).to.equal(0);
|
|
159
249
|
container.append(el2);
|
|
160
250
|
await Promise.resolve();
|
|
161
251
|
expect(el2.mountedCount).to.equal(2);
|
|
252
|
+
expect(el2.unmountedCount).to.equal(1, '重新挂载时不再执行已经执行过的清理函数');
|
|
162
253
|
expect(el2.renderCount).to.equal(1);
|
|
163
254
|
el2.update();
|
|
164
255
|
el2.update();
|
|
@@ -169,8 +260,13 @@ describe('gem element 生命周期', () => {
|
|
|
169
260
|
expect(el3.appTitle).to.equal('test1');
|
|
170
261
|
expect(el3.appTitle2).to.equal('test2');
|
|
171
262
|
expect(el3.renderCount).to.equal(0);
|
|
263
|
+
// 挂载前更新不会造成挂载后多余的渲染
|
|
264
|
+
el3.update();
|
|
265
|
+
el3.appTitle = 'test3';
|
|
172
266
|
container.append(el3);
|
|
173
267
|
expect(el3.renderCount).to.equal(1);
|
|
268
|
+
await Promise.resolve();
|
|
269
|
+
expect(el3.renderCount).to.equal(1);
|
|
174
270
|
});
|
|
175
271
|
});
|
|
176
272
|
|
|
@@ -343,6 +439,16 @@ describe('gem element 继承', () => {
|
|
|
343
439
|
await Promise.resolve();
|
|
344
440
|
expect(window.name).to.equal(`${name}21`);
|
|
345
441
|
});
|
|
442
|
+
it('setAttribute 设置基类观察的 attribute', async () => {
|
|
443
|
+
const el: InheritGem1 = await fixture(html`<inherit-gem1></inherit-gem1>`);
|
|
444
|
+
el.setAttribute('app-title', 'c');
|
|
445
|
+
expect(el.appTitle).to.equal('c');
|
|
446
|
+
const el1: InheritGem = await fixture(html`<inherit-gem></inherit-gem>`);
|
|
447
|
+
el1.setAttribute('app-title2', 'd');
|
|
448
|
+
el1.setAttribute('app-title', 'e');
|
|
449
|
+
expect(el1.appTitle2).to.equal('d');
|
|
450
|
+
expect(el1.appTitle).to.equal('e');
|
|
451
|
+
});
|
|
346
452
|
});
|
|
347
453
|
@customElement('render-empty')
|
|
348
454
|
@shadow()
|
|
@@ -114,6 +114,19 @@ describe('基本 gem element 测试', () => {
|
|
|
114
114
|
expect(el.hasAttribute('attr')).to.equal(false);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
it('setAttribute/removeAttribute/toggleAttribute 设置观察的 attribute', async () => {
|
|
118
|
+
const el: GemDemo = await fixture(html`<gem-demo></gem-demo>`);
|
|
119
|
+
el.setAttribute('attr', 'a');
|
|
120
|
+
el.toggleAttribute('disabled');
|
|
121
|
+
expect(el.attr).to.equal('a');
|
|
122
|
+
expect(el.disabled).to.equal(true);
|
|
123
|
+
el.removeAttribute('attr');
|
|
124
|
+
expect(el.attr).to.equal('');
|
|
125
|
+
expect(el.hasAttribute('attr')).to.equal(false);
|
|
126
|
+
await Promise.resolve();
|
|
127
|
+
expect(el.shadowRoot?.textContent).to.equal('attr: , disabled: true, count: 0, prop: , state: ');
|
|
128
|
+
});
|
|
129
|
+
|
|
117
130
|
it('读取 prop', async () => {
|
|
118
131
|
const el: GemDemo = await fixture(html`<gem-demo .prop=${{ value: 'prop' }}></gem-demo>`);
|
|
119
132
|
expect(el.prop).to.deep.equal({ value: 'prop' });
|
package/src/test/history.test.ts
CHANGED
|
@@ -58,6 +58,14 @@ describe('history 测试', () => {
|
|
|
58
58
|
await aTimeout(10);
|
|
59
59
|
expect(history.getParams().title).to.equal('321');
|
|
60
60
|
});
|
|
61
|
+
it('keep title when only hash changed', async () => {
|
|
62
|
+
history.replace({ title: 'Page' });
|
|
63
|
+
// 模拟应用格式化 document.title
|
|
64
|
+
document.title = 'Page | Site';
|
|
65
|
+
location.hash = '#x';
|
|
66
|
+
await aTimeout(10);
|
|
67
|
+
expect(history.getParams().title).to.equal('Page');
|
|
68
|
+
});
|
|
61
69
|
it('Native pushState/replaceState', async () => {
|
|
62
70
|
const historyLength = window.history.length;
|
|
63
71
|
window.history.pushState({}, 'title', '/a');
|
|
@@ -73,6 +81,17 @@ describe('history 测试', () => {
|
|
|
73
81
|
expect(new URLSearchParams(location.search).get('tab')).to.equal('a');
|
|
74
82
|
expect(window.history.length - historyLength).to.equal(1);
|
|
75
83
|
});
|
|
84
|
+
it('Native pushState with hash', async () => {
|
|
85
|
+
let hashChangeCount = 0;
|
|
86
|
+
const onHashChange = () => hashChangeCount++;
|
|
87
|
+
addEventListener('hashchange', onHashChange);
|
|
88
|
+
window.history.pushState({}, '', '/c#a%20b');
|
|
89
|
+
removeEventListener('hashchange', onHashChange);
|
|
90
|
+
expect(window.location.pathname).to.equal('/c');
|
|
91
|
+
expect(window.location.hash).to.equal('#a%20b');
|
|
92
|
+
expect(history.getParams().hash).to.equal('#a b');
|
|
93
|
+
expect(hashChangeCount).to.equal(1);
|
|
94
|
+
});
|
|
76
95
|
it('pushIgnoreCloseHandle', async () => {
|
|
77
96
|
const { href } = window.location;
|
|
78
97
|
let state = { open: true };
|
package/src/test/theme.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createTheme } from '../helper/theme';
|
|
1
|
+
import { createDecoratorTheme, createScopedTheme, createTheme } from '../helper/theme';
|
|
2
2
|
import { aTimeout, expect } from './utils';
|
|
3
3
|
|
|
4
4
|
describe('theme 测试', () => {
|
|
@@ -14,4 +14,11 @@ describe('theme 测试', () => {
|
|
|
14
14
|
expect(getComputedStyle(el).width).to.equal('1px');
|
|
15
15
|
el.remove();
|
|
16
16
|
});
|
|
17
|
+
|
|
18
|
+
it('key 作为变量名后缀', () => {
|
|
19
|
+
expect(createTheme({ textColor: 'red' }, 'k1').textColor).to.equal('var(--text-color-k1)');
|
|
20
|
+
expect(createScopedTheme({ size: '1px' }, 'k2').size).to.equal('var(--size-k2)');
|
|
21
|
+
expect(createDecoratorTheme({ size: '1px' }, 'k3').size).to.equal('var(--size-k3)');
|
|
22
|
+
expect(createTheme({ size: '1px' }).size).to.not.equal(createTheme({ size: '1px' }).size, '没有 key 时随机');
|
|
23
|
+
});
|
|
17
24
|
});
|
package/src/test/utils.test.ts
CHANGED
|
@@ -116,6 +116,12 @@ describe('utils 测试', () => {
|
|
|
116
116
|
expect(bodyStyleSheet.cssRules.item(0).cssText.startsWith('@scope (body)')).to.true;
|
|
117
117
|
expect(bodyStyleSheet).to.equal(bodyStyleSheet);
|
|
118
118
|
expect(bodyStyleSheet).not.to.equal(cssSheet[SheetToken].getStyle(document.documentElement));
|
|
119
|
+
// 更新内容时保留 light DOM 的作用域
|
|
120
|
+
cssSheet[SheetToken].setContent('div { color: blue; }');
|
|
121
|
+
cssSheet[SheetToken].updateStyle();
|
|
122
|
+
expect(bodyStyleSheet.cssRules.item(0).cssText.startsWith('@scope (body)')).to.true;
|
|
123
|
+
expect(bodyStyleSheet.cssRules.item(0).cssText).to.include('blue');
|
|
124
|
+
expect(cssSheet[SheetToken].getStyle().cssRules.item(0).style.color).to.equal('blue');
|
|
119
125
|
});
|
|
120
126
|
it('raw/css', () => {
|
|
121
127
|
const title: any = undefined;
|
|
@@ -139,6 +145,10 @@ describe('utils 测试', () => {
|
|
|
139
145
|
expect(rules.item(1).style.background).to.equal('red');
|
|
140
146
|
expect(rules.item(1).cssRules.item(0).selectorText).to.equal('&:hover *');
|
|
141
147
|
expect(rules.item(1).cssRules.item(0).style.background).to.equal('blue');
|
|
148
|
+
// 有 `-` 的 key 保留原样,可以用来扩展其他样式表的类名
|
|
149
|
+
const extended = css({ [cssSheet.scroll]: styled`font-weight: bold;` });
|
|
150
|
+
expect(extended[cssSheet.scroll]).to.equal(cssSheet.scroll);
|
|
151
|
+
expect(extended[SheetToken].getStyle().cssRules.item(0).selectorText).to.equal(`.${cssSheet.scroll}`);
|
|
142
152
|
});
|
|
143
153
|
it('styleMap/classMap/exportPartsMap', () => {
|
|
144
154
|
expect(styleMap({ '--x': '1px', fontSize: '14px', content: `'*'` })).to.equal(
|
package/src/test/utils.ts
CHANGED
|
@@ -13,6 +13,62 @@ export function aTimeout(ms = 0) {
|
|
|
13
13
|
return new Promise((resolve) => setTimeout(() => resolve(null), ms));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export function createFakeTimers() {
|
|
17
|
+
type Task = { at: number; every?: number; fn: () => void };
|
|
18
|
+
const tasks = new Map<number, Task>();
|
|
19
|
+
const originals = {
|
|
20
|
+
setTimeout: window.setTimeout,
|
|
21
|
+
setInterval: window.setInterval,
|
|
22
|
+
clearTimeout: window.clearTimeout,
|
|
23
|
+
clearInterval: window.clearInterval,
|
|
24
|
+
};
|
|
25
|
+
const realClearTimeout = window.clearTimeout.bind(window);
|
|
26
|
+
let now = 0;
|
|
27
|
+
let nextId = -1;
|
|
28
|
+
|
|
29
|
+
const schedule = (fn: () => void, delay = 0, every?: number) => {
|
|
30
|
+
const id = nextId--;
|
|
31
|
+
tasks.set(id, { at: now + Math.max(delay, 0), every, fn });
|
|
32
|
+
return id;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
install: () => {
|
|
37
|
+
window.setTimeout = ((fn: () => void, delay?: number) =>
|
|
38
|
+
schedule(fn, delay)) as unknown as typeof window.setTimeout;
|
|
39
|
+
window.setInterval = ((fn: () => void, delay?: number) =>
|
|
40
|
+
schedule(fn, delay, Math.max(delay ?? 1, 1))) as unknown as typeof window.setInterval;
|
|
41
|
+
const clear = ((id: number) => {
|
|
42
|
+
if (!tasks.delete(id)) realClearTimeout(id);
|
|
43
|
+
}) as unknown as typeof window.clearTimeout;
|
|
44
|
+
window.clearTimeout = clear;
|
|
45
|
+
window.clearInterval = clear;
|
|
46
|
+
},
|
|
47
|
+
uninstall: () => {
|
|
48
|
+
window.setTimeout = originals.setTimeout;
|
|
49
|
+
window.setInterval = originals.setInterval;
|
|
50
|
+
window.clearTimeout = originals.clearTimeout;
|
|
51
|
+
window.clearInterval = originals.clearInterval;
|
|
52
|
+
tasks.clear();
|
|
53
|
+
},
|
|
54
|
+
tick: (ms: number) => {
|
|
55
|
+
const end = now + ms;
|
|
56
|
+
for (;;) {
|
|
57
|
+
let next: { id: number; task: Task } | undefined;
|
|
58
|
+
for (const [id, task] of tasks) {
|
|
59
|
+
if (task.at <= end && (!next || task.at < next.task.at)) next = { id, task };
|
|
60
|
+
}
|
|
61
|
+
if (!next) break;
|
|
62
|
+
now = next.task.at;
|
|
63
|
+
if (next.task.every === undefined) tasks.delete(next.id);
|
|
64
|
+
else next.task.at += next.task.every;
|
|
65
|
+
next.task.fn();
|
|
66
|
+
}
|
|
67
|
+
now = end;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
16
72
|
export async function fixture(template: TemplateResult) {
|
|
17
73
|
const wrapper = document.createElement('div');
|
|
18
74
|
document.body.appendChild(wrapper);
|