test(extension): unit suite for lib/ + version-consistency specs
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 4s
CI / frontend-build (push) Successful in 21s
extension / lint (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 43s
CI / integration (push) Successful in 3m53s
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 4s
CI / frontend-build (push) Successful in 21s
extension / lint (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 43s
CI / integration (push) Successful in 3m53s
extension/ had no test harness at all -- web-ext lint was the only signal, so
the URL-normalization fix in 8214afe shipped with nothing exercising it.
Adds vitest (mirroring frontend/vitest.config.js) and three specs:
- url.spec.js normalizeApiUrl / webRootFromApiUrl, including the #2393
regression: instance-root input must reach /api/credentials,
idempotence, trailing-slash and whitespace handling, and
that empty input never yields a bare "/api" (which
isConfigured() would read as configured).
- platforms.spec.js getPlatformFromUrl / isArtistPage, pinning the #1485
regression -- all three Patreon creator URL shapes
(bare, /c/, /cw/) plus inner pages, with nav pages
excluded -- and table-integrity checks.
- version.spec.js manifest.json and package.json versions in lockstep,
AMO-safe version format, and url.js ordered before api.js
in background.scripts (classic scripts share one scope, so
a reorder is a runtime ReferenceError with no build signal).
Specs load lib/*.js by evaluating the real file as a classic script
(test/helpers/loadLib.js) instead of adding module.exports shims to production
code that would never run in the browser. The suite therefore exercises exactly
the bytes packaged into the XPI.
Two packaging consequences, both handled:
- web-ext would otherwise bundle test/ and vitest.config.js INTO the XPI;
both are now in --ignore-files across all four web-ext scripts.
- ci.yml's extension-version guard must ignore the same paths, or editing a
spec would demand a pointless version bump. The dangerous drift direction is
the opposite one -- a guard exclusion for a file that DOES ship would let a
real change pass unnoticed -- so version.spec.js asserts every :(exclude) in
ci.yml appears in --ignore-files.
extension.yml also triggers on ci.yml now, since version.spec.js reads it.
Refs #2393, #2397
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { loadLib } from './helpers/loadLib.js'
|
||||
|
||||
const { normalizeApiUrl, webRootFromApiUrl } = loadLib('url.js', [
|
||||
'normalizeApiUrl',
|
||||
'webRootFromApiUrl'
|
||||
])
|
||||
|
||||
describe('normalizeApiUrl', () => {
|
||||
// The bug this exists for (issue #2393): the instance root was accepted and
|
||||
// stored verbatim, so every request went to /credentials instead of
|
||||
// /api/credentials. That path is a Vue router route, so the SPA catch-all
|
||||
// answered GET with 200 HTML and rejected POST with 405 — which read as a
|
||||
// backend bug rather than a URL one.
|
||||
it('appends /api to an instance root', () => {
|
||||
expect(normalizeApiUrl('http://curator.traefik.internal')).toBe(
|
||||
'http://curator.traefik.internal/api'
|
||||
)
|
||||
})
|
||||
|
||||
it('leaves an API root alone rather than doubling the suffix', () => {
|
||||
expect(normalizeApiUrl('http://curator.traefik.internal/api')).toBe(
|
||||
'http://curator.traefik.internal/api'
|
||||
)
|
||||
})
|
||||
|
||||
it('is idempotent', () => {
|
||||
const once = normalizeApiUrl('http://curator.example.com')
|
||||
expect(normalizeApiUrl(once)).toBe(once)
|
||||
})
|
||||
|
||||
it('strips trailing slashes before deciding', () => {
|
||||
expect(normalizeApiUrl('http://curator.example.com/')).toBe('http://curator.example.com/api')
|
||||
expect(normalizeApiUrl('http://curator.example.com///')).toBe('http://curator.example.com/api')
|
||||
expect(normalizeApiUrl('http://curator.example.com/api/')).toBe('http://curator.example.com/api')
|
||||
})
|
||||
|
||||
it('trims surrounding whitespace (paste artifacts)', () => {
|
||||
expect(normalizeApiUrl(' http://curator.example.com ')).toBe(
|
||||
'http://curator.example.com/api'
|
||||
)
|
||||
})
|
||||
|
||||
it('matches the /api suffix case-insensitively', () => {
|
||||
expect(normalizeApiUrl('http://curator.example.com/API')).toBe('http://curator.example.com/API')
|
||||
})
|
||||
|
||||
it('returns empty string for empty/nullish input, never a bare "/api"', () => {
|
||||
// isConfigured() gates on truthiness, so a bogus '/api' here would read as
|
||||
// "configured" and produce a request against the options page's own origin.
|
||||
expect(normalizeApiUrl('')).toBe('')
|
||||
expect(normalizeApiUrl(' ')).toBe('')
|
||||
expect(normalizeApiUrl(null)).toBe('')
|
||||
expect(normalizeApiUrl(undefined)).toBe('')
|
||||
})
|
||||
|
||||
it('does not treat a path merely containing "api" as the suffix', () => {
|
||||
expect(normalizeApiUrl('http://curator.example.com/apiary')).toBe(
|
||||
'http://curator.example.com/apiary/api'
|
||||
)
|
||||
})
|
||||
|
||||
it('preserves a subpath deployment', () => {
|
||||
expect(normalizeApiUrl('http://host.internal/curator')).toBe('http://host.internal/curator/api')
|
||||
})
|
||||
})
|
||||
|
||||
describe('webRootFromApiUrl', () => {
|
||||
// The SPA root, where the Vue router and the served XPI live. Used by
|
||||
// OPEN_ARTIST_PAGE and the self-update check — NOT the JSON API.
|
||||
it('strips the /api suffix', () => {
|
||||
expect(webRootFromApiUrl('http://curator.example.com/api')).toBe('http://curator.example.com')
|
||||
})
|
||||
|
||||
it('accepts an instance root unchanged', () => {
|
||||
expect(webRootFromApiUrl('http://curator.example.com')).toBe('http://curator.example.com')
|
||||
})
|
||||
|
||||
it('agrees with normalizeApiUrl in both directions', () => {
|
||||
for (const input of ['http://curator.example.com', 'http://curator.example.com/api']) {
|
||||
expect(normalizeApiUrl(webRootFromApiUrl(input))).toBe(normalizeApiUrl(input))
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves a subpath deployment', () => {
|
||||
expect(webRootFromApiUrl('http://host.internal/curator/api')).toBe('http://host.internal/curator')
|
||||
})
|
||||
|
||||
it('returns empty string for empty/nullish input', () => {
|
||||
expect(webRootFromApiUrl('')).toBe('')
|
||||
expect(webRootFromApiUrl(null)).toBe('')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user