442 lines
12 KiB
TypeScript
442 lines
12 KiB
TypeScript
// 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,
|
|
};
|
|
}
|