const { test } = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const vm = require('node:vm') const ts = require('typescript') const React = require('react') const { renderToStaticMarkup } = require('react-dom/server') function harness(initialNow = 1000) { let now = initialNow, state, effect, interval, cleared = 0 const windowEvents = new Map(), documentEvents = new Map() class Clock extends Date { static now() { return now } } const exports = {} vm.runInNewContext(ts.transpileModule(fs.readFileSync(require.resolve('../src/renderer/src/components/AgentWorkDuration.tsx'), 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, jsx: ts.JsxEmit.ReactJSX } }).outputText, { exports, Date: Clock, window: { setInterval(callback, delay) { assert.equal(delay, 1000); interval = callback; return 7 }, clearInterval(id) { assert.equal(id, 7); interval = undefined; cleared++ }, addEventListener: (name, callback) => windowEvents.set(name, callback), removeEventListener: name => windowEvents.delete(name) }, document: { addEventListener: (name, callback) => documentEvents.set(name, callback), removeEventListener: name => documentEvents.delete(name) }, require(name) { if (name === '../i18n') return { t: (key, values) => key.replace(/\{\{seconds\}\}/g, values.seconds), useLocaleRender() {} } if (name === 'react') return { ...React, useState(initial) { state ??= typeof initial === 'function' ? initial() : initial; return [state, value => { state = value }] }, useEffect(callback) { effect = callback } } return require(name) } }) return { render: props => renderToStaticMarkup(exports.AgentWorkDuration({ dark: true, ...props })), mount: () => effect(), advance: ms => { now += ms }, tick: () => interval?.(), visible: () => documentEvents.get('visibilitychange')?.(), focus: () => windowEvents.get('focus')?.(), interval: () => interval, cleared: () => cleared, listeners: () => windowEvents.size + documentEvents.size } } test('running duration is based on start time, not the number of interval ticks', () => { const h = harness() assert.match(h.render({ startedAt: 1000 }), /0초 동안 작업 중입니다/) const cleanup = h.mount() h.advance(12_900) h.tick() assert.match(h.render({ startedAt: 1000 }), /12초 동안 작업 중입니다/) h.advance(60_000) h.visible() assert.match(h.render({ startedAt: 1000 }), /72초 동안 작업 중입니다/) h.advance(5_000) h.focus() assert.match(h.render({ startedAt: 1000 }), /77초 동안 작업 중입니다/) assert.equal(h.listeners(), 2) cleanup() assert.equal(h.interval(), undefined) assert.equal(h.listeners(), 0) assert.equal(h.cleared(), 1) }) test('returning to a remounted chat shows the full elapsed time immediately', () => { const h = harness(74_000) assert.match(h.render({ startedAt: 1000 }), /73초 동안 작업 중입니다/) }) test('completed duration stays fixed and never starts a timer', () => { const h = harness() assert.match(h.render({ startedAt: 1000, durationMs: 20_900 }), /20초 동안 작업함/) assert.equal(h.mount(), undefined) h.advance(60_000) h.tick() assert.match(h.render({ durationMs: 20_900 }), /20초 동안 작업함/) assert.equal(h.interval(), undefined) assert.equal(h.listeners(), 0) assert.match(h.render({ durationMs: 0 }), /0초 동안 작업함/) }) test('legacy replies and invalid timing do not invent an elapsed duration', () => { const h = harness() for (const durationMs of [undefined, -1, NaN, Infinity]) assert.equal(h.render({ durationMs }), '') assert.match(h.render({ startedAt: 2000 }), /0초 동안 작업 중입니다/) })