ether-to-astro 1.0.0
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/.env.example +13 -0
- package/.github/pull_request_template.md +16 -0
- package/.github/workflows/release.yml +35 -0
- package/.github/workflows/test.yml +32 -0
- package/AGENTS.md +99 -0
- package/LICENSE +18 -0
- package/NOTICE.md +45 -0
- package/README.md +301 -0
- package/SETUP.md +70 -0
- package/TESTING_SUMMARY.md +238 -0
- package/TEST_SUITE_STATUS.md +218 -0
- package/biome.json +48 -0
- package/dist/astro-service.d.ts +98 -0
- package/dist/astro-service.js +496 -0
- package/dist/chart-types.d.ts +52 -0
- package/dist/chart-types.js +51 -0
- package/dist/charts.d.ts +125 -0
- package/dist/charts.js +324 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +472 -0
- package/dist/constants.d.ts +81 -0
- package/dist/constants.js +76 -0
- package/dist/eclipses.d.ts +85 -0
- package/dist/eclipses.js +184 -0
- package/dist/ephemeris.d.ts +120 -0
- package/dist/ephemeris.js +379 -0
- package/dist/formatter.d.ts +2 -0
- package/dist/formatter.js +22 -0
- package/dist/houses.d.ts +82 -0
- package/dist/houses.js +169 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +150 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +31 -0
- package/dist/logger.d.ts +25 -0
- package/dist/logger.js +73 -0
- package/dist/profile-store.d.ts +48 -0
- package/dist/profile-store.js +156 -0
- package/dist/riseset.d.ts +82 -0
- package/dist/riseset.js +185 -0
- package/dist/storage.d.ts +10 -0
- package/dist/storage.js +40 -0
- package/dist/time-utils.d.ts +68 -0
- package/dist/time-utils.js +136 -0
- package/dist/tool-registry.d.ts +35 -0
- package/dist/tool-registry.js +307 -0
- package/dist/tool-result.d.ts +175 -0
- package/dist/tool-result.js +188 -0
- package/dist/transits.d.ts +108 -0
- package/dist/transits.js +263 -0
- package/dist/types.d.ts +450 -0
- package/dist/types.js +161 -0
- package/example-usage.md +131 -0
- package/natal-chart.json +187 -0
- package/package.json +61 -0
- package/scripts/download-ephemeris.js +115 -0
- package/setup.sh +21 -0
- package/src/astro-service.ts +710 -0
- package/src/chart-types.ts +125 -0
- package/src/charts.ts +399 -0
- package/src/cli.ts +694 -0
- package/src/constants.ts +89 -0
- package/src/eclipses.ts +226 -0
- package/src/ephemeris.ts +437 -0
- package/src/formatter.ts +25 -0
- package/src/houses.ts +202 -0
- package/src/index.ts +170 -0
- package/src/loader.ts +36 -0
- package/src/logger.ts +104 -0
- package/src/profile-store.ts +285 -0
- package/src/riseset.ts +229 -0
- package/src/time-utils.ts +167 -0
- package/src/tool-registry.ts +357 -0
- package/src/tool-result.ts +283 -0
- package/src/transits.ts +352 -0
- package/src/types.ts +547 -0
- package/tests/README.md +173 -0
- package/tests/TESTING_STRATEGY.md +178 -0
- package/tests/fixtures/bowen-yang-chart.ts +69 -0
- package/tests/fixtures/calculate-expected.ts +81 -0
- package/tests/fixtures/expected-results.ts +117 -0
- package/tests/fixtures/generate-expected-simple.ts +94 -0
- package/tests/helpers/date-fixtures.ts +15 -0
- package/tests/helpers/ephem.ts +11 -0
- package/tests/helpers/temp.ts +9 -0
- package/tests/setup.ts +11 -0
- package/tests/unit/astro-service.test.ts +323 -0
- package/tests/unit/chart-types.test.ts +18 -0
- package/tests/unit/charts-errors.test.ts +42 -0
- package/tests/unit/charts.test.ts +157 -0
- package/tests/unit/cli-commands.test.ts +82 -0
- package/tests/unit/cli-profiles.test.ts +128 -0
- package/tests/unit/cli.test.ts +191 -0
- package/tests/unit/constants.test.ts +26 -0
- package/tests/unit/correctness-critical.test.ts +408 -0
- package/tests/unit/eclipses.test.ts +108 -0
- package/tests/unit/ephemeris.test.ts +213 -0
- package/tests/unit/error-handling.test.ts +116 -0
- package/tests/unit/formatter.test.ts +29 -0
- package/tests/unit/houses-errors.test.ts +27 -0
- package/tests/unit/houses-validation.test.ts +164 -0
- package/tests/unit/houses.test.ts +205 -0
- package/tests/unit/profile-store.test.ts +163 -0
- package/tests/unit/real-user-charts.test.ts +148 -0
- package/tests/unit/riseset.test.ts +106 -0
- package/tests/unit/solver-edges.test.ts +197 -0
- package/tests/unit/time-utils-temporal.test.ts +303 -0
- package/tests/unit/time-utils.test.ts +173 -0
- package/tests/unit/tool-registry.test.ts +222 -0
- package/tests/unit/tool-result.test.ts +45 -0
- package/tests/unit/transit-correctness.test.ts +78 -0
- package/tests/unit/transits.test.ts +238 -0
- package/tests/validation/README.md +32 -0
- package/tests/validation/adapters/astrolog.ts +306 -0
- package/tests/validation/adapters/internal.ts +184 -0
- package/tests/validation/compare/eclipses.ts +47 -0
- package/tests/validation/compare/houses.ts +76 -0
- package/tests/validation/compare/positions.ts +104 -0
- package/tests/validation/compare/riseSet.ts +48 -0
- package/tests/validation/compare/roots.ts +90 -0
- package/tests/validation/compare/transits.ts +69 -0
- package/tests/validation/fixtures/astrolog-parity/core.ts +194 -0
- package/tests/validation/fixtures/eclipses/core.ts +14 -0
- package/tests/validation/fixtures/houses/core.ts +47 -0
- package/tests/validation/fixtures/positions/core.ts +159 -0
- package/tests/validation/fixtures/rise-set/core.ts +20 -0
- package/tests/validation/fixtures/roots/core.ts +47 -0
- package/tests/validation/fixtures/transits/core.ts +61 -0
- package/tests/validation/fixtures/transits/dst.ts +21 -0
- package/tests/validation/oracle.spec.ts +129 -0
- package/tests/validation/utils/denseRootOracle.ts +269 -0
- package/tests/validation/utils/fixtureTypes.ts +146 -0
- package/tests/validation/utils/report.ts +60 -0
- package/tests/validation/utils/tolerances.ts +23 -0
- package/tests/validation/validation.spec.ts +836 -0
- package/tools/color-picker.html +388 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +31 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { runCli } from '../../src/cli.js';
|
|
6
|
+
|
|
7
|
+
function parseJson(text: string): Record<string, unknown> {
|
|
8
|
+
return JSON.parse(text) as Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe.sequential('CLI profile behavior', () => {
|
|
12
|
+
let originalCwd: string;
|
|
13
|
+
let originalProfile: string | undefined;
|
|
14
|
+
let originalProfileFile: string | undefined;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
originalCwd = process.cwd();
|
|
18
|
+
originalProfile = process.env.ASTRO_PROFILE;
|
|
19
|
+
originalProfileFile = process.env.ASTRO_PROFILE_FILE;
|
|
20
|
+
delete process.env.ASTRO_PROFILE;
|
|
21
|
+
delete process.env.ASTRO_PROFILE_FILE;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
process.chdir(originalCwd);
|
|
26
|
+
if (originalProfile === undefined) delete process.env.ASTRO_PROFILE;
|
|
27
|
+
else process.env.ASTRO_PROFILE = originalProfile;
|
|
28
|
+
if (originalProfileFile === undefined) delete process.env.ASTRO_PROFILE_FILE;
|
|
29
|
+
else process.env.ASTRO_PROFILE_FILE = originalProfileFile;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('profiles show returns PROFILE_FILE_NOT_FOUND when profile file is missing', async () => {
|
|
33
|
+
const missingPath = path.join(tmpdir(), `astro-missing-profile-${Date.now()}.json`);
|
|
34
|
+
const stdout: string[] = [];
|
|
35
|
+
const stderr: string[] = [];
|
|
36
|
+
|
|
37
|
+
const code = await runCli(
|
|
38
|
+
['profiles', 'show', '--profile', 'elwyn', '--profile-file', missingPath],
|
|
39
|
+
{
|
|
40
|
+
stdout: (msg) => stdout.push(msg),
|
|
41
|
+
stderr: (msg) => stderr.push(msg),
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(code).toBe(1);
|
|
46
|
+
expect(stdout).toEqual([]);
|
|
47
|
+
const payload = parseJson(stderr.join('\n'));
|
|
48
|
+
expect(payload.code).toBe('PROFILE_FILE_NOT_FOUND');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('non-profile command falls back to UTC when local profile file is malformed', async () => {
|
|
52
|
+
const cwd = path.join(tmpdir(), `astro-cli-malformed-${Date.now()}`);
|
|
53
|
+
await mkdir(cwd, { recursive: true });
|
|
54
|
+
await writeFile(path.join(cwd, '.astro.json'), '{invalid json', 'utf8');
|
|
55
|
+
process.chdir(cwd);
|
|
56
|
+
|
|
57
|
+
const stdout: string[] = [];
|
|
58
|
+
const stderr: string[] = [];
|
|
59
|
+
const code = await runCli(['get-next-eclipses'], {
|
|
60
|
+
stdout: (msg) => stdout.push(msg),
|
|
61
|
+
stderr: (msg) => stderr.push(msg),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(code).toBe(0);
|
|
65
|
+
expect(stderr).toEqual([]);
|
|
66
|
+
const payload = parseJson(stdout.join('\n'));
|
|
67
|
+
expect(payload.timezone).toBe('UTC');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('non-profile command returns INVALID_PROFILE_FILE when profile is explicitly requested', async () => {
|
|
71
|
+
const cwd = path.join(tmpdir(), `astro-cli-explicit-profile-${Date.now()}`);
|
|
72
|
+
await mkdir(cwd, { recursive: true });
|
|
73
|
+
await writeFile(path.join(cwd, '.astro.json'), '{invalid json', 'utf8');
|
|
74
|
+
process.chdir(cwd);
|
|
75
|
+
|
|
76
|
+
const stdout: string[] = [];
|
|
77
|
+
const stderr: string[] = [];
|
|
78
|
+
const code = await runCli(['get-next-eclipses', '--profile', 'elwyn'], {
|
|
79
|
+
stdout: (msg) => stdout.push(msg),
|
|
80
|
+
stderr: (msg) => stderr.push(msg),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(code).toBe(1);
|
|
84
|
+
expect(stdout).toEqual([]);
|
|
85
|
+
const payload = parseJson(stderr.join('\n'));
|
|
86
|
+
expect(payload.code).toBe('INVALID_PROFILE_FILE');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('profiles commands resolve explicit relative profile paths from injected cwd', async () => {
|
|
90
|
+
const cwd = path.join(tmpdir(), `astro-cli-relative-profile-${Date.now()}`);
|
|
91
|
+
await mkdir(cwd, { recursive: true });
|
|
92
|
+
await writeFile(
|
|
93
|
+
path.join(cwd, 'profiles.json'),
|
|
94
|
+
JSON.stringify({
|
|
95
|
+
version: 1,
|
|
96
|
+
defaultProfile: 'elwyn',
|
|
97
|
+
profiles: {
|
|
98
|
+
elwyn: {
|
|
99
|
+
name: 'Elwyn',
|
|
100
|
+
year: 1990,
|
|
101
|
+
month: 1,
|
|
102
|
+
day: 1,
|
|
103
|
+
hour: 1,
|
|
104
|
+
minute: 1,
|
|
105
|
+
latitude: 1,
|
|
106
|
+
longitude: 1,
|
|
107
|
+
timezone: 'UTC',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
'utf8'
|
|
112
|
+
);
|
|
113
|
+
process.chdir(cwd);
|
|
114
|
+
|
|
115
|
+
const stdout: string[] = [];
|
|
116
|
+
const stderr: string[] = [];
|
|
117
|
+
const code = await runCli(['profiles', 'show', '--profile', 'elwyn', '--profile-file', 'profiles.json'], {
|
|
118
|
+
stdout: (msg) => stdout.push(msg),
|
|
119
|
+
stderr: (msg) => stderr.push(msg),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(code).toBe(0);
|
|
123
|
+
expect(stderr).toEqual([]);
|
|
124
|
+
const payload = parseJson(stdout.join('\n'));
|
|
125
|
+
expect(path.basename(String(payload.filePath))).toBe('profiles.json');
|
|
126
|
+
expect(String(payload.filePath)).toContain(path.basename(cwd));
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { runCli } from '../../src/cli.js';
|
|
5
|
+
import { makeTempDir } from '../helpers/temp.js';
|
|
6
|
+
|
|
7
|
+
function parseJson(text: string): Record<string, unknown> {
|
|
8
|
+
return JSON.parse(text) as Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe.sequential('When running CLI commands', () => {
|
|
12
|
+
let originalCwd: string;
|
|
13
|
+
let originalProfile: string | undefined;
|
|
14
|
+
let originalProfileFile: string | undefined;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
originalCwd = process.cwd();
|
|
18
|
+
originalProfile = process.env.ASTRO_PROFILE;
|
|
19
|
+
originalProfileFile = process.env.ASTRO_PROFILE_FILE;
|
|
20
|
+
delete process.env.ASTRO_PROFILE;
|
|
21
|
+
delete process.env.ASTRO_PROFILE_FILE;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
process.chdir(originalCwd);
|
|
26
|
+
if (originalProfile === undefined) delete process.env.ASTRO_PROFILE;
|
|
27
|
+
else process.env.ASTRO_PROFILE = originalProfile;
|
|
28
|
+
if (originalProfileFile === undefined) delete process.env.ASTRO_PROFILE_FILE;
|
|
29
|
+
else process.env.ASTRO_PROFILE_FILE = originalProfileFile;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('Given default output mode, then JSON is emitted; and with --pretty, human text is emitted', async () => {
|
|
33
|
+
const stdout: string[] = [];
|
|
34
|
+
const stderr: string[] = [];
|
|
35
|
+
|
|
36
|
+
const codeJson = await runCli(['get-next-eclipses'], {
|
|
37
|
+
stdout: (m) => stdout.push(m),
|
|
38
|
+
stderr: (m) => stderr.push(m),
|
|
39
|
+
});
|
|
40
|
+
expect(codeJson).toBe(0);
|
|
41
|
+
expect(stderr).toEqual([]);
|
|
42
|
+
expect(parseJson(stdout.join('\n'))).toHaveProperty('timezone');
|
|
43
|
+
|
|
44
|
+
stdout.length = 0;
|
|
45
|
+
const codePretty = await runCli(['get-next-eclipses', '--pretty'], {
|
|
46
|
+
stdout: (m) => stdout.push(m),
|
|
47
|
+
stderr: (m) => stderr.push(m),
|
|
48
|
+
});
|
|
49
|
+
expect(codePretty).toBe(0);
|
|
50
|
+
expect(stdout.join('\n')).toContain('ether-to-astro');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('Given profile file in cwd, then command resolves profile timezone from cwd context', async () => {
|
|
54
|
+
const dir = await makeTempDir('cli-profile');
|
|
55
|
+
await writeFile(
|
|
56
|
+
path.join(dir, '.astro.json'),
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
version: 1,
|
|
59
|
+
defaultProfile: 'default',
|
|
60
|
+
profiles: {
|
|
61
|
+
default: {
|
|
62
|
+
name: 'Test',
|
|
63
|
+
year: 1990,
|
|
64
|
+
month: 1,
|
|
65
|
+
day: 1,
|
|
66
|
+
hour: 1,
|
|
67
|
+
minute: 1,
|
|
68
|
+
latitude: 1,
|
|
69
|
+
longitude: 1,
|
|
70
|
+
timezone: 'UTC',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
'utf8'
|
|
75
|
+
);
|
|
76
|
+
process.chdir(dir);
|
|
77
|
+
const stdout: string[] = [];
|
|
78
|
+
|
|
79
|
+
const code = await runCli(['get-next-eclipses'], {
|
|
80
|
+
stdout: (m) => stdout.push(m),
|
|
81
|
+
stderr: vi.fn(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(code).toBe(0);
|
|
85
|
+
const payload = parseJson(stdout.join('\n'));
|
|
86
|
+
expect(payload.timezone).toBe('UTC');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('Given relative natal file path from cwd, then natal-dependent command executes', async () => {
|
|
90
|
+
const dir = await makeTempDir('cli-natal-file');
|
|
91
|
+
await writeFile(
|
|
92
|
+
path.join(dir, 'natal.json'),
|
|
93
|
+
JSON.stringify({
|
|
94
|
+
name: 'Test',
|
|
95
|
+
year: 1990,
|
|
96
|
+
month: 1,
|
|
97
|
+
day: 1,
|
|
98
|
+
hour: 1,
|
|
99
|
+
minute: 1,
|
|
100
|
+
latitude: 1,
|
|
101
|
+
longitude: 1,
|
|
102
|
+
timezone: 'UTC',
|
|
103
|
+
}),
|
|
104
|
+
'utf8'
|
|
105
|
+
);
|
|
106
|
+
process.chdir(dir);
|
|
107
|
+
|
|
108
|
+
const code = await runCli(['get-houses', '--natal-file', 'natal.json'], {
|
|
109
|
+
stdout: vi.fn(),
|
|
110
|
+
stderr: vi.fn(),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(code).toBe(0);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('Given a missing profile file, then CLI returns a structured JSON error payload', async () => {
|
|
117
|
+
const stderr: string[] = [];
|
|
118
|
+
const code = await runCli(
|
|
119
|
+
['profiles', 'show', '--profile', 'missing', '--profile-file', '/tmp/does-not-exist.json'],
|
|
120
|
+
{ stdout: vi.fn(), stderr: (m) => stderr.push(m) }
|
|
121
|
+
);
|
|
122
|
+
expect(code).toBe(1);
|
|
123
|
+
const payload = parseJson(stderr.join('\n'));
|
|
124
|
+
expect(payload.code).toBe('PROFILE_FILE_NOT_FOUND');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('Given a natal-dependent command with no natal context, then PROFILE_NOT_FOUND is returned', async () => {
|
|
128
|
+
const stderr: string[] = [];
|
|
129
|
+
const code = await runCli(
|
|
130
|
+
['get-houses'],
|
|
131
|
+
{ stdout: vi.fn(), stderr: (m) => stderr.push(m) }
|
|
132
|
+
);
|
|
133
|
+
expect(code).toBe(1);
|
|
134
|
+
const payload = parseJson(stderr.join('\n'));
|
|
135
|
+
expect(payload.code).toBe('PROFILE_NOT_FOUND');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('Given --pretty on failure, then error output is human-readable rather than JSON', async () => {
|
|
139
|
+
const stderr: string[] = [];
|
|
140
|
+
const code = await runCli(
|
|
141
|
+
['get-transits', '--pretty', '--year', 'abc'],
|
|
142
|
+
{ stdout: vi.fn(), stderr: (m) => stderr.push(m) }
|
|
143
|
+
);
|
|
144
|
+
expect(code).toBe(1);
|
|
145
|
+
expect(stderr.join('\n')).not.toContain('{');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('Given set-natal-chart without inline/file/profile input, then PROFILE_NOT_FOUND is returned', async () => {
|
|
149
|
+
const stderr: string[] = [];
|
|
150
|
+
const code = await runCli(
|
|
151
|
+
['set-natal-chart'],
|
|
152
|
+
{ stdout: vi.fn(), stderr: (m) => stderr.push(m) }
|
|
153
|
+
);
|
|
154
|
+
expect(code).toBe(1);
|
|
155
|
+
const payload = parseJson(stderr.join('\n'));
|
|
156
|
+
expect(payload.code).toBe('PROFILE_NOT_FOUND');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('Given profiles show for a missing profile key, then PROFILE_NOT_FOUND is returned', async () => {
|
|
160
|
+
const dir = await makeTempDir('cli-show-missing');
|
|
161
|
+
const file = path.join(dir, '.astro.json');
|
|
162
|
+
await writeFile(
|
|
163
|
+
file,
|
|
164
|
+
JSON.stringify({
|
|
165
|
+
version: 1,
|
|
166
|
+
profiles: {
|
|
167
|
+
default: {
|
|
168
|
+
name: 'Default',
|
|
169
|
+
year: 1990,
|
|
170
|
+
month: 1,
|
|
171
|
+
day: 1,
|
|
172
|
+
hour: 1,
|
|
173
|
+
minute: 1,
|
|
174
|
+
latitude: 1,
|
|
175
|
+
longitude: 1,
|
|
176
|
+
timezone: 'UTC',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
'utf8'
|
|
181
|
+
);
|
|
182
|
+
const stderr: string[] = [];
|
|
183
|
+
const code = await runCli(
|
|
184
|
+
['profiles', 'show', '--profile', 'ghost', '--profile-file', file],
|
|
185
|
+
{ stdout: vi.fn(), stderr: (m) => stderr.push(m) }
|
|
186
|
+
);
|
|
187
|
+
expect(code).toBe(1);
|
|
188
|
+
const payload = parseJson(stderr.join('\n'));
|
|
189
|
+
expect(payload.code).toBe('PROFILE_NOT_FOUND');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getDefaultTheme } from '../../src/constants.js';
|
|
3
|
+
|
|
4
|
+
describe('When deriving default chart theme from local time', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.useRealTimers();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('Given evening local hour in target timezone, then default theme is dark', () => {
|
|
10
|
+
vi.useFakeTimers();
|
|
11
|
+
vi.setSystemTime(new Date('2024-03-26T23:00:00Z'));
|
|
12
|
+
expect(getDefaultTheme('America/New_York')).toBe('dark');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('Given daytime local hour in target timezone, then default theme is light', () => {
|
|
16
|
+
vi.useFakeTimers();
|
|
17
|
+
vi.setSystemTime(new Date('2024-03-26T17:00:00Z'));
|
|
18
|
+
expect(getDefaultTheme('America/New_York')).toBe('light');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('Given no timezone override, then server-local hour determines the theme', () => {
|
|
22
|
+
vi.useFakeTimers();
|
|
23
|
+
vi.setSystemTime(new Date('2024-03-26T12:00:00Z'));
|
|
24
|
+
expect(getDefaultTheme()).toMatch(/light|dark/);
|
|
25
|
+
});
|
|
26
|
+
});
|