web: smoke-test the built standalone app against a mock graphql cms
This commit is contained in:
@@ -0,0 +1,441 @@
|
||||
// Canned GraphQL responses for the smoke-test mock CMS.
|
||||
//
|
||||
// Every piece is typed with `satisfies` against the *fragment* types generated
|
||||
// by codegen (fragment masking makes the query types too weak — their
|
||||
// `$fragmentRefs` props are optional). When `npm run codegen` regenerates
|
||||
// after a schema change, `tsc --noEmit` fails here, flagging stale fixtures.
|
||||
|
||||
import type {
|
||||
AllAssociationSlugsQuery,
|
||||
AllAssociationsQuery,
|
||||
AllGenericSlugsQuery,
|
||||
AllVenueSlugsQuery,
|
||||
AssociationBySlugQuery,
|
||||
AssociationFragment,
|
||||
AssociationIndexFragment,
|
||||
ContactIndexFragment,
|
||||
ContactsQuery,
|
||||
EventBySlugQuery,
|
||||
EventCategoryFragment,
|
||||
EventFragment,
|
||||
EventIndexFragment,
|
||||
EventIndexMetadataQuery,
|
||||
EventListItemFragment,
|
||||
EventOrganizerFragment,
|
||||
EventOverviewItemFragment,
|
||||
FutureEventsQuery,
|
||||
GenericFragment,
|
||||
GenericPageByUrlQuery,
|
||||
HomeFragment,
|
||||
HomeQuery,
|
||||
ImageFragment,
|
||||
NewsBySlugQuery,
|
||||
NewsFragment,
|
||||
NewsIndexFragment,
|
||||
NewsIndexMetadataQuery,
|
||||
NewsListItemFragment,
|
||||
NewsQuery,
|
||||
OpeningHoursSetFragment,
|
||||
OpeningHoursSetsQuery,
|
||||
PreviewPageQuery,
|
||||
SearchQuery,
|
||||
SponsorsPageFragment,
|
||||
SponsorsQuery,
|
||||
StudioFragment,
|
||||
StudioQuery,
|
||||
VenueBySlugQuery,
|
||||
VenueFragment,
|
||||
VenueIndexFragment,
|
||||
VenueIndexQuery,
|
||||
VenueRentalIndexFragment,
|
||||
VenueRentalIndexQuery,
|
||||
} from "@/gql/graphql";
|
||||
|
||||
export type MockState = {
|
||||
newsHeadlineVersion: "v1" | "v2";
|
||||
};
|
||||
|
||||
export const SLUGS = {
|
||||
genericUrl: "praktisk", // served at /praktisk, wagtail urlPath /home/praktisk/
|
||||
association: "testforening",
|
||||
venue: "storsalen",
|
||||
news: "test-artikkel",
|
||||
event: "testkonsert",
|
||||
} as const;
|
||||
|
||||
export function newsTitle(state: MockState): string {
|
||||
return `Testartikkel (${state.newsHeadlineVersion})`;
|
||||
}
|
||||
|
||||
type Resolver = (vars: Record<string, unknown>, state: MockState) => unknown;
|
||||
export type Fixtures = Record<string, Resolver>;
|
||||
|
||||
// Fragment masking types a fragment spread's slot as an opaque ref, but the
|
||||
// wire format is simply the fragment's fields. Each piece above proves its
|
||||
// shape with `satisfies <Fragment>`; this cast lets it slot into the masked
|
||||
// position of the enclosing query/fragment type.
|
||||
function mask<T>(value: T): { " $fragmentRefs"?: undefined } {
|
||||
return value as never;
|
||||
}
|
||||
|
||||
export function makeFixtures(cmsBaseUrl: string): Fixtures {
|
||||
const inAWeek = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||
const occurrenceStart = inAWeek.toISOString();
|
||||
const occurrenceEnd = new Date(
|
||||
inAWeek.getTime() + 2 * 60 * 60 * 1000
|
||||
).toISOString();
|
||||
|
||||
const image = {
|
||||
id: "1",
|
||||
url: `${cmsBaseUrl}/media/pig.png`,
|
||||
width: 64,
|
||||
height: 64,
|
||||
alt: "En gris",
|
||||
attribution: null,
|
||||
} satisfies ImageFragment;
|
||||
|
||||
const eventCategory = {
|
||||
__typename: "EventCategory",
|
||||
name: "Konsert",
|
||||
slug: "konsert",
|
||||
pig: "PIG_PINK",
|
||||
showInFilters: true,
|
||||
} satisfies EventCategoryFragment;
|
||||
|
||||
const eventOrganizer = {
|
||||
__typename: "EventOrganizer",
|
||||
id: "1",
|
||||
name: "Testforeningen",
|
||||
slug: "testforeningen",
|
||||
externalUrl: null,
|
||||
association: null,
|
||||
} satisfies EventOrganizerFragment;
|
||||
|
||||
const eventListItem = {
|
||||
__typename: "EventPage",
|
||||
id: "10",
|
||||
slug: SLUGS.event,
|
||||
title: "Testkonsert",
|
||||
subtitle: "En kveld med smoke tests",
|
||||
featuredImage: mask(image),
|
||||
occurrences: [
|
||||
{
|
||||
__typename: "EventOccurrence",
|
||||
id: "100",
|
||||
start: occurrenceStart,
|
||||
end: occurrenceEnd,
|
||||
},
|
||||
],
|
||||
} satisfies EventListItemFragment;
|
||||
|
||||
const venuePageRef = {
|
||||
__typename: "VenuePage" as const,
|
||||
id: "30",
|
||||
slug: SLUGS.venue,
|
||||
title: "Storsalen",
|
||||
preposition: "i",
|
||||
url: `/lokaler/${SLUGS.venue}/`,
|
||||
};
|
||||
|
||||
const eventOverviewItem = {
|
||||
...eventListItem,
|
||||
categories: [mask(eventCategory)],
|
||||
occurrences: [
|
||||
{
|
||||
__typename: "EventOccurrence",
|
||||
id: "100",
|
||||
start: occurrenceStart,
|
||||
end: occurrenceEnd,
|
||||
venueCustom: null,
|
||||
venue: venuePageRef,
|
||||
},
|
||||
],
|
||||
organizers: [mask(eventOrganizer)],
|
||||
} satisfies EventOverviewItemFragment;
|
||||
|
||||
const eventPage = {
|
||||
...eventOverviewItem,
|
||||
seoTitle: "Testkonsert",
|
||||
searchDescription: null,
|
||||
lead: "Bli med på testkonsert.",
|
||||
pig: "PIG_PINK",
|
||||
facebookUrl: null,
|
||||
ticketUrl: null,
|
||||
free: true,
|
||||
priceRegular: null,
|
||||
priceMember: null,
|
||||
priceStudent: null,
|
||||
body: [],
|
||||
} satisfies EventFragment;
|
||||
|
||||
const eventIndex = {
|
||||
__typename: "EventIndex",
|
||||
id: "3",
|
||||
slug: "arrangementer",
|
||||
seoTitle: "Arrangementer",
|
||||
searchDescription: null,
|
||||
title: "Arrangementer",
|
||||
} satisfies EventIndexFragment;
|
||||
|
||||
const newsListItem = (state: MockState) =>
|
||||
({
|
||||
__typename: "NewsPage",
|
||||
id: "20",
|
||||
slug: SLUGS.news,
|
||||
title: newsTitle(state),
|
||||
firstPublishedAt: "2026-08-01T12:00:00+02:00",
|
||||
excerpt: "Et utdrag.",
|
||||
featuredImage: mask(image),
|
||||
}) satisfies NewsListItemFragment;
|
||||
|
||||
const newsPage = (state: MockState) =>
|
||||
({
|
||||
...newsListItem(state),
|
||||
seoTitle: newsTitle(state),
|
||||
searchDescription: null,
|
||||
lead: "Ingressen.",
|
||||
body: [],
|
||||
}) satisfies NewsFragment;
|
||||
|
||||
const newsIndex = {
|
||||
__typename: "NewsIndex",
|
||||
id: "2",
|
||||
slug: "aktuelt",
|
||||
seoTitle: "Aktuelt",
|
||||
searchDescription: null,
|
||||
title: "Aktuelt",
|
||||
lead: null,
|
||||
} satisfies NewsIndexFragment;
|
||||
|
||||
const home = {
|
||||
__typename: "HomePage",
|
||||
featuredEvents: [],
|
||||
} satisfies HomeFragment;
|
||||
|
||||
const genericPage = {
|
||||
__typename: "GenericPage",
|
||||
id: "40",
|
||||
urlPath: `/home/${SLUGS.genericUrl}/`,
|
||||
seoTitle: "Praktisk info",
|
||||
searchDescription: null,
|
||||
title: "Praktisk info",
|
||||
lead: "Alt du trenger å vite.",
|
||||
pig: "PIG_PINK",
|
||||
body: [],
|
||||
} satisfies GenericFragment;
|
||||
|
||||
const association = {
|
||||
__typename: "AssociationPage",
|
||||
id: "50",
|
||||
slug: SLUGS.association,
|
||||
title: "Testforeningen",
|
||||
seoTitle: "Testforeningen",
|
||||
searchDescription: null,
|
||||
excerpt: "En forening for testing.",
|
||||
lead: null,
|
||||
associationType: "Kultur",
|
||||
websiteUrl: null,
|
||||
body: [],
|
||||
logo: null,
|
||||
} satisfies AssociationFragment;
|
||||
|
||||
const associationIndex = {
|
||||
__typename: "AssociationIndex",
|
||||
title: "Foreninger",
|
||||
seoTitle: "Foreninger",
|
||||
searchDescription: null,
|
||||
lead: null,
|
||||
body: [],
|
||||
} satisfies AssociationIndexFragment;
|
||||
|
||||
const venue = {
|
||||
__typename: "VenuePage",
|
||||
id: "30",
|
||||
slug: SLUGS.venue,
|
||||
title: "Storsalen",
|
||||
seoTitle: "Storsalen",
|
||||
searchDescription: null,
|
||||
showAsBookable: true,
|
||||
showInOverview: true,
|
||||
floor: "2",
|
||||
preposition: "i",
|
||||
usedFor: "Konserter",
|
||||
techSpecsUrl: null,
|
||||
capabilityAudio: null,
|
||||
capabilityAudioVideo: null,
|
||||
capabilityBar: null,
|
||||
capabilityLighting: null,
|
||||
capacityLegal: "450",
|
||||
capacityStanding: "450",
|
||||
capacitySitting: "200",
|
||||
images: [],
|
||||
body: [],
|
||||
featuredImage: null,
|
||||
} satisfies VenueFragment;
|
||||
|
||||
const venueIndex = {
|
||||
__typename: "VenueIndex",
|
||||
title: "Lokaler",
|
||||
seoTitle: "Lokaler",
|
||||
searchDescription: null,
|
||||
lead: null,
|
||||
body: [],
|
||||
} satisfies VenueIndexFragment;
|
||||
|
||||
const venueRentalIndex = {
|
||||
__typename: "VenueRentalIndex",
|
||||
title: "Utleie",
|
||||
seoTitle: "Utleie",
|
||||
searchDescription: null,
|
||||
lead: null,
|
||||
body: [],
|
||||
} satisfies VenueRentalIndexFragment;
|
||||
|
||||
const contactIndex = {
|
||||
__typename: "ContactIndex",
|
||||
title: "Kontakt",
|
||||
seoTitle: "Kontakt",
|
||||
searchDescription: null,
|
||||
lead: null,
|
||||
body: [],
|
||||
} satisfies ContactIndexFragment;
|
||||
|
||||
const sponsorsPage = {
|
||||
__typename: "SponsorsPage",
|
||||
title: "Sponsorer",
|
||||
seoTitle: "Sponsorer",
|
||||
searchDescription: null,
|
||||
lead: null,
|
||||
body: [],
|
||||
sponsors: [],
|
||||
} satisfies SponsorsPageFragment;
|
||||
|
||||
const studioPage = {
|
||||
__typename: "StudioPage",
|
||||
id: "60",
|
||||
title: "Studio",
|
||||
seoTitle: "Studio",
|
||||
searchDescription: null,
|
||||
lead: null,
|
||||
pig: "PIG_PINK",
|
||||
logo: null,
|
||||
body: [],
|
||||
} satisfies StudioFragment;
|
||||
|
||||
const openingHoursSet = {
|
||||
name: "Vanlige åpningstider",
|
||||
effectiveFrom: "2026-01-01",
|
||||
effectiveTo: null,
|
||||
announcement: null,
|
||||
items: [],
|
||||
} satisfies OpeningHoursSetFragment;
|
||||
|
||||
return {
|
||||
// --- build time: generateStaticParams ---
|
||||
allGenericSlugs: () =>
|
||||
({
|
||||
pages: [{ id: "40", urlPath: `/home/${SLUGS.genericUrl}/` }],
|
||||
}) satisfies AllGenericSlugsQuery,
|
||||
allAssociationSlugs: () =>
|
||||
({
|
||||
pages: [{ id: "50", slug: SLUGS.association }],
|
||||
}) satisfies AllAssociationSlugsQuery,
|
||||
allVenueSlugs: () =>
|
||||
({
|
||||
pages: [{ id: "30", slug: SLUGS.venue }],
|
||||
}) satisfies AllVenueSlugsQuery,
|
||||
|
||||
// --- build time: static pages ---
|
||||
home: (_vars, state) =>
|
||||
({
|
||||
events: { futureEvents: [mask(eventListItem)] },
|
||||
home: mask(home),
|
||||
news: [mask(newsListItem(state))],
|
||||
}) satisfies HomeQuery,
|
||||
news: (_vars, state) =>
|
||||
({
|
||||
index: mask(newsIndex),
|
||||
news: [mask(newsListItem(state))],
|
||||
}) satisfies NewsQuery,
|
||||
newsIndexMetadata: () =>
|
||||
({ index: mask(newsIndex) }) satisfies NewsIndexMetadataQuery,
|
||||
futureEvents: () =>
|
||||
({
|
||||
index: mask(eventIndex),
|
||||
events: { futureEvents: [mask(eventOverviewItem)] },
|
||||
eventCategories: [mask(eventCategory)],
|
||||
eventOrganizers: [mask(eventOrganizer)],
|
||||
venues: [
|
||||
{
|
||||
id: "30",
|
||||
title: "Storsalen",
|
||||
slug: SLUGS.venue,
|
||||
preposition: "i",
|
||||
},
|
||||
],
|
||||
}) satisfies FutureEventsQuery,
|
||||
eventIndexMetadata: () =>
|
||||
({ index: mask(eventIndex) }) satisfies EventIndexMetadataQuery,
|
||||
allAssociations: () =>
|
||||
({
|
||||
index: mask(associationIndex),
|
||||
associations: [mask(association)],
|
||||
}) satisfies AllAssociationsQuery,
|
||||
contacts: () => ({ index: mask(contactIndex) }) satisfies ContactsQuery,
|
||||
venueIndex: () =>
|
||||
({ index: mask(venueIndex), venues: [mask(venue)] }) satisfies VenueIndexQuery,
|
||||
venueRentalIndex: () =>
|
||||
({
|
||||
index: mask(venueRentalIndex),
|
||||
venues: [mask(venue)],
|
||||
}) satisfies VenueRentalIndexQuery,
|
||||
sponsors: () => ({ page: mask(sponsorsPage) }) satisfies SponsorsQuery,
|
||||
studio: () => ({ page: mask(studioPage) }) satisfies StudioQuery,
|
||||
openingHoursSets: () =>
|
||||
({ openingHoursSets: [mask(openingHoursSet)] }) satisfies OpeningHoursSetsQuery,
|
||||
|
||||
// --- build time: prerendered dynamic pages (null for unknown slugs → notFound) ---
|
||||
genericPageByUrl: (vars) =>
|
||||
({
|
||||
page:
|
||||
vars.urlPath === `/home/${SLUGS.genericUrl}/` ? mask(genericPage) : null,
|
||||
}) satisfies GenericPageByUrlQuery,
|
||||
associationBySlug: (vars) =>
|
||||
({
|
||||
association: vars.slug === SLUGS.association ? mask(association) : null,
|
||||
}) satisfies AssociationBySlugQuery,
|
||||
venueBySlug: (vars) =>
|
||||
({
|
||||
venue: vars.slug === SLUGS.venue ? mask(venue) : null,
|
||||
}) satisfies VenueBySlugQuery,
|
||||
|
||||
// --- runtime ---
|
||||
newsBySlug: (vars, state) =>
|
||||
({
|
||||
news: vars.slug === SLUGS.news ? mask(newsPage(state)) : null,
|
||||
}) satisfies NewsBySlugQuery,
|
||||
eventBySlug: (vars) =>
|
||||
({
|
||||
event: vars.slug === SLUGS.event ? mask(eventPage) : null,
|
||||
}) satisfies EventBySlugQuery,
|
||||
search: (vars) =>
|
||||
({
|
||||
results:
|
||||
typeof vars.query === "string" && vars.query.length > 0
|
||||
? [
|
||||
{
|
||||
__typename: "GenericPage",
|
||||
id: "40",
|
||||
title: "Praktisk info",
|
||||
url: `/${SLUGS.genericUrl}/`,
|
||||
lead: "Alt du trenger å vite.",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
}) satisfies SearchQuery,
|
||||
previewPage: (_vars, state) =>
|
||||
({
|
||||
page: { __typename: "NewsPage" as const, ...mask(newsPage(state)) },
|
||||
}) satisfies PreviewPageQuery,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user