mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
c7d71ea57c
* feat(whats-new): remote release notes with dev workspace mode Add WHATS_NEW.md, CI whats-new.md asset upload, and client fetch/cache with embedded fallbacks. Dev and -dev builds read the full file from the repo for debugging; RC/stable download the release asset on first use. * fix(whats-new): render ## headings and add changelog tab Parse h2 sections in release-notes markdown; load changelog alongside highlights and let users switch views on the What's New page. * fix(whats-new): prefetch on startup and fix CI typecheck prebuild Prefetch whats-new asset when the shell loads on RC/stable builds. Run prebuild:release-notes before tsc and coverage jobs so the gitignored generated bundle exists in CI. * docs: CHANGELOG and credits for What's New remote notes (PR #1058) * fix(whats-new): always slice embedded release notes to current line Drop full CHANGELOG embed for -dev bundles; tauri:dev still reads live markdown from the repo. Ignore all of src/generated/ in git. * fix(whats-new): fetch release asset via Rust to bypass CORS Route whats-new.md download through fetch_url_bytes; rename the technical tab label; add fetch unit tests (PR #1058 review).
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const fetchWhatsNewAsset = vi.fn();
|
|
const readReleaseNotesCache = vi.fn();
|
|
const writeReleaseNotesCache = vi.fn();
|
|
|
|
vi.mock('./releaseNotesChannel', () => ({
|
|
isDevChannelVersion: (version: string) => /-dev(?:\b|$)/i.test(version.trim()),
|
|
isWorkspaceReleaseNotesMode: (version: string) => /-dev(?:\b|$)/i.test(version.trim()),
|
|
}));
|
|
|
|
vi.mock('./releaseNotesFetch', () => ({
|
|
fetchWhatsNewAsset: (...args: unknown[]) => fetchWhatsNewAsset(...args),
|
|
}));
|
|
|
|
vi.mock('./releaseNotesCache', () => ({
|
|
readReleaseNotesCache: (...args: unknown[]) => readReleaseNotesCache(...args),
|
|
writeReleaseNotesCache: (...args: unknown[]) => writeReleaseNotesCache(...args),
|
|
}));
|
|
|
|
vi.mock('../../generated/releaseNotesBundle', () => ({
|
|
WHATS_NEW_RAW: `## [1.48.0] - 2026-06-10
|
|
|
|
## Highlights
|
|
- Embedded what's new`,
|
|
CHANGELOG_RAW: `## [1.48.0]
|
|
|
|
## Added
|
|
- Embedded changelog fallback`,
|
|
}));
|
|
|
|
const { resolveReleaseNotes } = await import('./releaseNotesResolve');
|
|
|
|
describe('resolveReleaseNotes (shipped channel)', () => {
|
|
beforeEach(() => {
|
|
fetchWhatsNewAsset.mockReset();
|
|
readReleaseNotesCache.mockReset();
|
|
writeReleaseNotesCache.mockReset();
|
|
readReleaseNotesCache.mockResolvedValue(null);
|
|
fetchWhatsNewAsset.mockResolvedValue(null);
|
|
});
|
|
|
|
it('uses cache before network', async () => {
|
|
readReleaseNotesCache.mockResolvedValue('Cached body line');
|
|
|
|
const r = await resolveReleaseNotes('1.48.0');
|
|
expect(r.source).toBe('cache');
|
|
expect(r.entry?.body).toContain('Cached body line');
|
|
expect(fetchWhatsNewAsset).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('fetches remote and writes cache when no cache hit', async () => {
|
|
fetchWhatsNewAsset.mockResolvedValue('Fresh remote body');
|
|
|
|
const r = await resolveReleaseNotes('1.48.0-rc.1');
|
|
expect(r.source).toBe('remote');
|
|
expect(r.entry?.body).toBe('Fresh remote body');
|
|
expect(writeReleaseNotesCache).toHaveBeenCalledWith('1.48.0-rc.1', 'Fresh remote body');
|
|
});
|
|
|
|
it('falls back to embedded whats-new when offline', async () => {
|
|
const r = await resolveReleaseNotes('1.48.0');
|
|
expect(r.source).toBe('embedded-whats-new');
|
|
expect(r.entry?.body).toContain("Embedded what's new");
|
|
expect(r.entry?.date).toBe('2026-06-10');
|
|
});
|
|
});
|
|
|
|
describe('resolveReleaseNotes (workspace)', () => {
|
|
beforeEach(() => {
|
|
fetchWhatsNewAsset.mockReset();
|
|
readReleaseNotesCache.mockReset();
|
|
});
|
|
|
|
it('skips fetch for -dev versions', async () => {
|
|
const r = await resolveReleaseNotes('1.48.0-dev');
|
|
expect(['workspace', 'workspace-changelog']).toContain(r.source);
|
|
expect(fetchWhatsNewAsset).not.toHaveBeenCalled();
|
|
expect(r.entry?.body.length).toBeGreaterThan(0);
|
|
});
|
|
});
|