@lexriver/dome 2.0.2 → 2.0.3

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.
@@ -1,252 +0,0 @@
1
- import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
2
- import { DomeComponent } from './DomeComponent.mjs'
3
- import { DomeManipulator } from './DomeManipulator.mjs'
4
- import type { Animation } from './Animation.mjs'
5
-
6
- class FakeElement {
7
- readonly nodeType = 1
8
- readonly childNodes: FakeElement[] = []
9
- readonly classList = {
10
- values: new Set<string>(),
11
- add: (name:string) => this.classList.values.add(name),
12
- remove: (name:string) => this.classList.values.delete(name)
13
- }
14
- parentNode: FakeElement | null = null
15
- throwOnAppend = false
16
-
17
- get firstChild() {
18
- return this.childNodes[0] ?? null
19
- }
20
-
21
- appendChild(child:FakeElement) {
22
- if(child.throwOnAppend){
23
- throw new Error('append failed')
24
- }
25
- child.parentNode = this
26
- this.childNodes.push(child)
27
- return child
28
- }
29
-
30
- remove() {
31
- if(!this.parentNode) return
32
- const index = this.parentNode.childNodes.indexOf(this)
33
- if(index >= 0){
34
- this.parentNode.childNodes.splice(index, 1)
35
- }
36
- this.parentNode = null
37
- }
38
- }
39
-
40
- const asElement = (element:FakeElement) => element as unknown as Element
41
- const animation = (name:string):Animation => ({ cssClassName: name, timeMs: 5 })
42
- const waitAsync = (timeMs:number) => new Promise<void>(resolve => setTimeout(resolve, timeMs))
43
-
44
- async function waitForAsync(condition:() => boolean) {
45
- const timeoutAt = Date.now() + 500
46
- while(!condition()){
47
- if(Date.now() >= timeoutAt){
48
- throw new Error('Timed out waiting for condition')
49
- }
50
- await waitAsync(1)
51
- }
52
- }
53
-
54
- function deferred() {
55
- let resolve!:() => void
56
- const promise = new Promise<void>(resolvePromise => {
57
- resolve = resolvePromise
58
- })
59
- return { promise, resolve }
60
- }
61
-
62
- const originalNode = Object.getOwnPropertyDescriptor(globalThis, 'Node')
63
-
64
- beforeAll(() => {
65
- Object.defineProperty(globalThis, 'Node', {
66
- configurable: true,
67
- value: { ELEMENT_NODE: 1 }
68
- })
69
- })
70
-
71
- afterAll(() => {
72
- if(originalNode){
73
- Object.defineProperty(globalThis, 'Node', originalNode)
74
- } else {
75
- delete (globalThis as { Node?:unknown }).Node
76
- }
77
- })
78
-
79
- describe('DomeManipulator.replaceAllChildrenAsync', () => {
80
- async function expectLastQueuedReplacement(
81
- animationForHide?:Animation,
82
- animationForShow?:Animation
83
- ) {
84
- const container = new FakeElement()
85
- const existing = new FakeElement()
86
- const first = new FakeElement()
87
- const second = new FakeElement()
88
- container.appendChild(existing)
89
-
90
- await Promise.all([
91
- DomeManipulator.replaceAllChildrenAsync(asElement(container), asElement(first), animationForHide, animationForShow),
92
- DomeManipulator.replaceAllChildrenAsync(asElement(container), asElement(second), animationForHide, animationForShow)
93
- ])
94
-
95
- expect(container.childNodes).toEqual([second])
96
- }
97
-
98
- it('serializes two concurrent replacements without animations', async () => {
99
- await expectLastQueuedReplacement()
100
- })
101
-
102
- it('serializes concurrent replacements with a hide animation', async () => {
103
- await expectLastQueuedReplacement(animation('hide'))
104
- })
105
-
106
- it('serializes concurrent replacements with a show animation', async () => {
107
- await expectLastQueuedReplacement(undefined, animation('show'))
108
- })
109
-
110
- it('serializes concurrent replacements with hide and show animations', async () => {
111
- await expectLastQueuedReplacement(animation('hide'), animation('show'))
112
- })
113
-
114
- it('runs replacements on different containers independently', async () => {
115
- const slowContainer = new FakeElement()
116
- const fastContainer = new FakeElement()
117
- const oldSlowChild = new FakeElement()
118
- const slowChild = new FakeElement()
119
- const fastChild = new FakeElement()
120
- slowContainer.appendChild(oldSlowChild)
121
-
122
- let slowReplacementFinished = false
123
- const slowReplacement = DomeManipulator.replaceAllChildrenAsync(
124
- asElement(slowContainer),
125
- asElement(slowChild),
126
- { cssClassName: 'hide', timeMs: 30 }
127
- ).then(() => {
128
- slowReplacementFinished = true
129
- })
130
- await waitAsync(1)
131
-
132
- await DomeManipulator.replaceAllChildrenAsync(asElement(fastContainer), asElement(fastChild))
133
-
134
- expect(fastContainer.childNodes).toEqual([fastChild])
135
- expect(slowReplacementFinished).toBe(false)
136
- expect(slowContainer.childNodes).toEqual([oldSlowChild])
137
- await slowReplacement
138
- })
139
-
140
- it('does not let a failed replacement block a later replacement', async () => {
141
- const container = new FakeElement()
142
- const failingChild = new FakeElement()
143
- const successfulChild = new FakeElement()
144
- failingChild.throwOnAppend = true
145
-
146
- const failedReplacement = DomeManipulator.replaceAllChildrenAsync(
147
- asElement(container),
148
- asElement(failingChild)
149
- )
150
- const queuedReplacement = DomeManipulator.replaceAllChildrenAsync(
151
- asElement(container),
152
- asElement(successfulChild)
153
- )
154
-
155
- await expect(failedReplacement).rejects.toThrow('append failed')
156
- await queuedReplacement
157
- expect(container.childNodes).toEqual([successfulChild])
158
- })
159
- })
160
-
161
- class ScheduledTestComponent extends DomeComponent<{}> {
162
- updateCount = 0
163
- activeUpdates = 0
164
- maximumActiveUpdates = 0
165
- updateBehavior:() => Promise<void> = async () => undefined
166
-
167
- protected render():HTMLElement {
168
- return {} as HTMLElement
169
- }
170
-
171
- override async updateAsync() {
172
- this.updateCount++
173
- this.activeUpdates++
174
- this.maximumActiveUpdates = Math.max(this.maximumActiveUpdates, this.activeUpdates)
175
- try {
176
- await this.updateBehavior()
177
- } finally {
178
- this.activeUpdates--
179
- }
180
- }
181
- }
182
-
183
- describe('DomeComponent.scheduleUpdate', () => {
184
- it('debounces repeated requests made before an update starts', async () => {
185
- const component = new ScheduledTestComponent({}, null)
186
-
187
- await Promise.all([
188
- component.scheduleUpdate(),
189
- component.scheduleUpdate(),
190
- component.scheduleUpdate()
191
- ])
192
-
193
- expect(component.updateCount).toBe(1)
194
- })
195
-
196
- it('coalesces requests during an update into exactly one follow-up update', async () => {
197
- const component = new ScheduledTestComponent({}, null)
198
- const firstUpdate = deferred()
199
- component.updateBehavior = () => component.updateCount === 1
200
- ? firstUpdate.promise
201
- : Promise.resolve()
202
-
203
- const initialSchedule = component.scheduleUpdate()
204
- await waitForAsync(() => component.updateCount === 1)
205
- component.scheduleUpdate()
206
- component.scheduleUpdate()
207
- component.scheduleUpdate()
208
- await waitAsync(10)
209
- firstUpdate.resolve()
210
- await initialSchedule
211
-
212
- expect(component.updateCount).toBe(2)
213
- })
214
-
215
- it('never runs scheduled updates concurrently for one component', async () => {
216
- const component = new ScheduledTestComponent({}, null)
217
- const firstUpdate = deferred()
218
- component.updateBehavior = () => component.updateCount === 1
219
- ? firstUpdate.promise
220
- : Promise.resolve()
221
-
222
- const initialSchedule = component.scheduleUpdate()
223
- await waitForAsync(() => component.updateCount === 1)
224
- component.scheduleUpdate()
225
- await waitAsync(10)
226
-
227
- expect(component.maximumActiveUpdates).toBe(1)
228
- firstUpdate.resolve()
229
- await initialSchedule
230
- expect(component.maximumActiveUpdates).toBe(1)
231
- })
232
-
233
- it('allows subsequent scheduled updates after updateAsync rejects', async () => {
234
- const component = new ScheduledTestComponent({}, null)
235
- const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined)
236
- component.updateBehavior = async () => {
237
- if(component.updateCount === 1){
238
- throw new Error('update failed')
239
- }
240
- }
241
-
242
- try {
243
- await component.scheduleUpdate()
244
- await component.scheduleUpdate()
245
- } finally {
246
- consoleError.mockRestore()
247
- }
248
-
249
- expect(component.updateCount).toBe(2)
250
- expect(component.maximumActiveUpdates).toBe(1)
251
- })
252
- })
package/src/index.mts DELETED
@@ -1,12 +0,0 @@
1
- export * from '@lexriver/async'
2
- export * from '@lexriver/data-types'
3
- export * from '@lexriver/observable'
4
- export * from '@lexriver/type-event'
5
- export * from './AnimatedArray.mjs'
6
- export * from './AnimatedTable.mjs'
7
- export * from './AnimatedText.mjs'
8
- export * from './Dome.mjs'
9
- export * from './DomeComponent.mjs'
10
- export * from './DomeManipulator.mjs'
11
- export * from './DomeRouter.console-test.mjs'
12
-
package/src/temp-test.mts DELETED
@@ -1,23 +0,0 @@
1
- import { LongestCommonSubsequence } from "./LongestCommonSubsequence.mjs"
2
-
3
- let oldArray = "ABCD".split('')
4
- let newArray = "AXYZBCD345".split('')
5
- const countOfOperations = LongestCommonSubsequence.getPatchOrdered({
6
- oldArray: [...oldArray],
7
- newArray: newArray,
8
- onRemove: (index, item) => {
9
- console.log('-', item, index)
10
- oldArray.splice(index,1)
11
- },
12
- onAdd: (index, item) => {
13
- console.log('+', item, index)
14
- oldArray.splice(index,0,item)
15
- }
16
- })
17
- console.log('expecting equal', oldArray, newArray)
18
- console.log('countOfOperations=', countOfOperations)
19
- //expect(oldArray).toEqual(newArray)
20
- // console.log('result oldArray=', oldArray)
21
- // console.log('result nweArray=', newArray)
22
-
23
-
package/tsconfig.json DELETED
@@ -1,59 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- /* Basic Options */
4
- //"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
5
- // "target": "es5",
6
- //"target":"ESNext",
7
- "target": "ESNext",
8
- "moduleResolution": "NodeNext",
9
- "module": "NodeNext",
10
- //"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
11
- // "lib": [], /* Specify library files to be included in the compilation. */
12
- // "allowJs": true, /* Allow javascript files to be compiled. */
13
- // "checkJs": true, /* Report errors in .js files. */
14
- // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
15
- "declaration": true, /* Generates corresponding '.d.ts' file. */
16
- // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
17
- // "sourceMap": true, /* Generates corresponding '.map' file. */
18
- // "outFile": "./", /* Concatenate and emit output to single file. */
19
- "outDir": "./out", /* Redirect output structure to the directory. */
20
- // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
21
- // "composite": true, /* Enable project compilation */
22
- // "removeComments": true, /* Do not emit comments to output. */
23
- // "noEmit": true, /* Do not emit outputs. */
24
- // "importHelpers": true, /* Import emit helpers from 'tslib'. */
25
- "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
26
- // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
27
- /* Strict Type-Checking Options */
28
- "strict": true, /* Enable all strict type-checking options. */
29
- "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
30
- // "strictNullChecks": true, /* Enable strict null checks. */
31
- // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32
- // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
33
- // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
34
- // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
35
- /* Additional Checks */
36
- // "noUnusedLocals": true, /* Report errors on unused locals. */
37
- // "noUnusedParameters": true, /* Report errors on unused parameters. */
38
- // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
39
- // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
40
- /* Module Resolution Options */
41
- // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
42
- // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
43
- // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
44
- // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
45
- // "typeRoots": [], /* List of folders to include type definitions from. */
46
- // "types": [], /* Type declaration files to be included in compilation. */
47
- // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
48
- "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
49
- // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
50
- /* Source Map Options */
51
- // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
52
- // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
53
- // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
54
- // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
55
- /* Experimental Options */
56
- // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
57
- // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
58
- },
59
- }
package/vitest.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from 'vitest/config'
2
-
3
- export default defineConfig({
4
- test:{
5
- exclude: [
6
- './out/**',
7
- './node_modules/**'
8
- ]
9
- }
10
- })