diff --git a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts index 2f76a7a..337f498 100644 --- a/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts +++ b/cocoscreator_projects/YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts @@ -81,7 +81,11 @@ export class LocalPlatformLogin extends Component { waitForMinimumDisplay: async () => { await firstDraw; const drawnAt = this.required(this.firstDrawAt, 'loading first draw'); - await this.delay(Math.max(0, MINIMUM_LOADING_MS - (performance.now() - drawnAt))); + let remaining = MINIMUM_LOADING_MS - (performance.now() - drawnAt); + while (remaining > 0) { + await this.delay(remaining); + remaining = MINIMUM_LOADING_MS - (performance.now() - drawnAt); + } this.record({ kind: 'minimum-display-ready', data: performance.now() - drawnAt }); }, createSocket: url => new WebSocket(url), record: event => this.record(event), diff --git a/cocoscreator_projects/framework-tests/local-platform-login/view.test.ts b/cocoscreator_projects/framework-tests/local-platform-login/view.test.ts index 3b7320b..1dbed17 100644 --- a/cocoscreator_projects/framework-tests/local-platform-login/view.test.ts +++ b/cocoscreator_projects/framework-tests/local-platform-login/view.test.ts @@ -143,3 +143,57 @@ test('component stop cancels pending draw and timer work and disables spinner up assert.equal(timers.size, 0); assert.equal(director.listenerCount('draw'), 0); assert.equal(component.getDiagnostics().pendingCount, 0); }); + +test('minimum display ignores an early timer wake and cancellation clears its remaining wait', async () => { + const path = new URL('../../YouleNexus/assets/scripts/local-platform-login/LocalPlatformLogin.ts', import.meta.url); + const exports: any = {}; const timers = new Map void>(); let timerId = 0; let clock = 100; + let hostOptions: any; + runInNewContext(ts.transpileModule(readFileSync(path, 'utf8'), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, experimentalDecorators: true }, + }).outputText, { exports, require: (name: string) => { + if (name === 'cc/env') return { DEBUG: true }; + if (name === 'cc') return { Component: class {}, Node, Label, Sprite, UITransform, Prefab: class {}, sys: { isNative: false }, + _decorator: { ccclass: () => (target: unknown) => target, property: () => () => {} } }; + if (name.endsWith('local-login-host.ts')) return { createLocalLoginHost: (options: unknown) => { + hostOptions = options; return { start: async () => {}, stop() {}, getSnapshot: () => ({}) }; + } }; + if (name.endsWith('local-login-view.ts')) return load(); + if (name.endsWith('remote-config-fetcher.ts')) return { HttpConfigFetcher: class {} }; + throw new Error(`Unexpected dependency ${name}`); + }, Error, performance: { now: () => clock }, window: { + location: { search: '?profile=local' }, localStorage: {}, + setTimeout: (callback: () => void) => { timers.set(++timerId, callback); return timerId; }, + clearTimeout: (id: number) => timers.delete(id), + } }); + async function started() { + const component = new exports.LocalPlatformLogin(); const f = fixture(); + Object.assign(component, f.refs, { stopButton: new Node(), loadingPrefab: {}, loginPrefab: {} }); + const loading = new Node('Layer614_Loading'); const group = new Node('group-40'); + const spinner = new Node('载入动画'); spinner.components.set(Sprite, new Sprite()); + spinner.components.set(UITransform, new UITransform()); group.children.push(spinner); + loading.children.push(group); component.loadingRoot.children.push(loading); + component.loadingRoot.getComponentsInChildren = () => []; + await component.start(); director.emit('draw'); await Promise.resolve(); + return component; + } + const drain = async () => { for (let i = 0; i < 5; i++) await Promise.resolve(); }; + function wake(at: number) { + clock = at; const [id, callback] = timers.entries().next().value!; + timers.delete(id); callback(); + } + const component = await started(); let completed = false; + const minimum = hostOptions.waitForMinimumDisplay().then(() => { completed = true; }); + await drain(); wake(599.8); await drain(); + assert.equal(completed, false, '499.8 ms elapsed must not open minimum display gate'); + assert.equal(component.getDiagnostics().events.some((event: any) => event.kind === 'minimum-display-ready'), false); + assert.equal(timers.size, 1); + wake(600); await minimum; assert.equal(completed, true); assert.equal(timers.size, 0); + component.stop(); + + clock = 1000; const cancelled = await started(); + const cancelledMinimum = hostOptions.waitForMinimumDisplay(); + const rejection = assert.rejects(cancelledMinimum, /cancelled/); + await drain(); wake(1499.8); await drain(); + assert.equal(timers.size, 1); cancelled.stop(); await rejection; + assert.equal(timers.size, 0); assert.equal(cancelled.getDiagnostics().pendingCount, 0); +});