import { describe, it, expect } from 'vitest' import { loadLib } from './helpers/loadLib.js' const { getPlatformFromUrl, isArtistPage, PLATFORMS, PLATFORM_ARTIST_PATTERNS } = loadLib( 'platforms.js', ['getPlatformFromUrl', 'isArtistPage', 'PLATFORMS', 'PLATFORM_ARTIST_PATTERNS'] ) describe('getPlatformFromUrl', () => { it('identifies each platform from a domain URL', () => { expect(getPlatformFromUrl('https://www.patreon.com/Atole')).toBe('patreon') expect(getPlatformFromUrl('https://subscribestar.adult/someone')).toBe('subscribestar') expect(getPlatformFromUrl('https://www.hentai-foundry.com/user/someone')).toBe('hentaifoundry') expect(getPlatformFromUrl('https://discord.com/channels/@me')).toBe('discord') expect(getPlatformFromUrl('https://www.pixiv.net/en/users/123')).toBe('pixiv') expect(getPlatformFromUrl('https://www.deviantart.com/someone')).toBe('deviantart') }) it('accepts http as well as https, with or without www', () => { expect(getPlatformFromUrl('http://patreon.com/Atole')).toBe('patreon') expect(getPlatformFromUrl('https://www.patreon.com/Atole')).toBe('patreon') }) it('returns null for unrelated hosts', () => { expect(getPlatformFromUrl('https://example.com/patreon.com')).toBe(null) expect(getPlatformFromUrl('https://not-patreon.com/Atole')).toBe(null) expect(getPlatformFromUrl('')).toBe(null) }) }) describe('isArtistPage', () => { // Regression cases from issue #1485: the Add-to-FC button vanished once the // operator SUBSCRIBED to a creator, because Patreon serves subscribed users // the /cw/ ("creator workspace") URL and the pattern only matched the bare // root. All three creator URL shapes must match, plus inner pages — the // button matters most exactly when you're subscribed. it('matches all three Patreon creator URL shapes', () => { expect(isArtistPage('https://www.patreon.com/Atole', 'patreon')).toBe(true) expect(isArtistPage('https://www.patreon.com/c/Atole', 'patreon')).toBe(true) expect(isArtistPage('https://www.patreon.com/cw/Atole', 'patreon')).toBe(true) }) it('matches Patreon creator inner pages', () => { expect(isArtistPage('https://www.patreon.com/cw/Atole/posts', 'patreon')).toBe(true) expect(isArtistPage('https://www.patreon.com/Atole/membership', 'patreon')).toBe(true) }) it('excludes Patreon navigation pages that are not creators', () => { for (const nav of ['home', 'search', 'messages', 'notifications', 'library', 'settings']) { expect(isArtistPage(`https://www.patreon.com/${nav}`, 'patreon')).toBe(false) expect(isArtistPage(`https://www.patreon.com/${nav}/anything`, 'patreon')).toBe(false) } }) it('matches SubscribeStar creator roots on both TLDs but not feed pages', () => { expect(isArtistPage('https://subscribestar.adult/someone', 'subscribestar')).toBe(true) expect(isArtistPage('https://subscribestar.com/someone', 'subscribestar')).toBe(true) expect(isArtistPage('https://subscribestar.adult/feed', 'subscribestar')).toBe(false) expect(isArtistPage('https://subscribestar.adult/messages', 'subscribestar')).toBe(false) }) it('matches Hentai Foundry user pages only', () => { expect(isArtistPage('https://www.hentai-foundry.com/user/someone', 'hentaifoundry')).toBe(true) expect(isArtistPage('https://www.hentai-foundry.com/pictures/popular', 'hentaifoundry')).toBe( false ) }) it('matches Pixiv numeric user pages, with or without the /en/ prefix', () => { expect(isArtistPage('https://www.pixiv.net/users/12345', 'pixiv')).toBe(true) expect(isArtistPage('https://www.pixiv.net/en/users/12345', 'pixiv')).toBe(true) expect(isArtistPage('https://www.pixiv.net/en/artworks/999', 'pixiv')).toBe(false) }) it('excludes DeviantArt navigation roots', () => { expect(isArtistPage('https://www.deviantart.com/someone', 'deviantart')).toBe(true) expect(isArtistPage('https://www.deviantart.com/home', 'deviantart')).toBe(false) expect(isArtistPage('https://www.deviantart.com/watch', 'deviantart')).toBe(false) }) it('returns false for a platform with no artist pattern (discord)', () => { expect(isArtistPage('https://discord.com/channels/@me', 'discord')).toBe(false) }) it('returns false for an unknown platform key', () => { expect(isArtistPage('https://www.patreon.com/Atole', 'nope')).toBe(false) }) }) describe('platform table integrity', () => { it('gives every artist pattern a corresponding platform entry', () => { // A pattern keyed to a platform that no longer exists is dead code that // silently never fires; the reverse (a platform with no pattern) is the // legitimate discord case, so only this direction is an error. for (const key of Object.keys(PLATFORM_ARTIST_PATTERNS)) { expect(Object.keys(PLATFORMS)).toContain(key) } }) it('gives every platform the fields the popup renders', () => { for (const [key, platform] of Object.entries(PLATFORMS)) { expect(platform.name, `${key}.name`).toBeTruthy() expect(platform.color, `${key}.color`).toMatch(/^#[0-9A-Fa-f]{6}$/) expect(['cookies', 'token'], `${key}.authType`).toContain(platform.authType) expect(platform.urlPattern, `${key}.urlPattern`).toBeInstanceOf(RegExp) expect(Array.isArray(platform.domains), `${key}.domains`).toBe(true) expect(platform.domains.length, `${key}.domains`).toBeGreaterThan(0) } }) it('keeps every artist URL matched by its own platform pattern too', () => { // isArtistPage is only ever consulted after getPlatformFromUrl resolves a // key, so an artist pattern matching a URL its platform's urlPattern // rejects would be unreachable. const samples = { patreon: 'https://www.patreon.com/cw/Atole', subscribestar: 'https://subscribestar.adult/someone', hentaifoundry: 'https://www.hentai-foundry.com/user/someone', deviantart: 'https://www.deviantart.com/someone', pixiv: 'https://www.pixiv.net/en/users/12345' } for (const [key, url] of Object.entries(samples)) { expect(isArtistPage(url, key), `${key} artist pattern`).toBe(true) expect(getPlatformFromUrl(url), `${key} urlPattern`).toBe(key) } }) })