mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
feat(vacay): add manual school holiday calendars
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
manualSchoolRegionId,
|
||||
schoolHolidayCountryRequestSchema,
|
||||
schoolHolidayPeriodSchema,
|
||||
schoolHolidayRegionRequestSchema,
|
||||
} from './school-holiday-catalog.schema';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('manual school holiday contracts', () => {
|
||||
it('accepts a named break spanning New Year and leap days', () => {
|
||||
expect(
|
||||
schoolHolidayPeriodSchema.parse({ name: ' Winter ', startDate: '2026-12-20', endDate: '2027-01-06' }).name,
|
||||
).toBe('Winter');
|
||||
expect(
|
||||
schoolHolidayPeriodSchema.safeParse({ name: 'Leap day', startDate: '2028-02-29', endDate: '2028-02-29' }).success,
|
||||
).toBe(true);
|
||||
});
|
||||
it.each([
|
||||
['2026-02-29', '2026-03-01'],
|
||||
['2026-04-31', '2026-05-01'],
|
||||
['2026-12-20', '2026-12-19'],
|
||||
['2026-01-01', '2028-01-01'],
|
||||
])('rejects invalid or unbounded dates %s to %s', (startDate, endDate) => {
|
||||
expect(schoolHolidayPeriodSchema.safeParse({ name: 'Break', startDate, endDate }).success).toBe(false);
|
||||
});
|
||||
it('requires a country code, names, revision and bounded periods', () => {
|
||||
expect(schoolHolidayCountryRequestSchema.safeParse({ code: '../', name: 'USA' }).success).toBe(false);
|
||||
expect(schoolHolidayCountryRequestSchema.safeParse({ code: 'US', name: ' ' }).success).toBe(false);
|
||||
expect(schoolHolidayRegionRequestSchema.safeParse({ name: 'District', revision: 0, holidays: [] }).success).toBe(
|
||||
true,
|
||||
);
|
||||
expect(schoolHolidayRegionRequestSchema.safeParse({ name: 'District', holidays: [] }).success).toBe(false);
|
||||
expect(
|
||||
schoolHolidayRegionRequestSchema.safeParse({
|
||||
name: 'District',
|
||||
revision: 0,
|
||||
holidays: Array(501).fill({ name: 'Break', startDate: '2026-01-01', endDate: '2026-01-02' }),
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
it('keeps manual ids distinct from provider subdivisions', () => {
|
||||
expect(manualSchoolRegionId('US-MANUAL-12')).toBe(12);
|
||||
for (const code of ['US-WA', 'NL|group:NL-NO', 'US-MANUAL-0', 'US-MANUAL-1junk'])
|
||||
expect(manualSchoolRegionId(code)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { idSchema, nonEmptyString } from '../common/primitives.schema';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
export const schoolHolidayCountryRequestSchema = z.object({
|
||||
code: z.string().regex(/^[A-Z]{2}$/),
|
||||
name: nonEmptyString.max(100),
|
||||
});
|
||||
export const schoolHolidayPeriodSchema = z
|
||||
.object({
|
||||
name: nonEmptyString.max(150),
|
||||
startDate: z.string().date(),
|
||||
endDate: z.string().date(),
|
||||
})
|
||||
.refine((period) => period.endDate >= period.startDate, {
|
||||
message: 'End date must be on or after start date',
|
||||
path: ['endDate'],
|
||||
})
|
||||
.refine((period) => Date.parse(period.endDate) - Date.parse(period.startDate) <= 366 * 86400000, {
|
||||
message: 'A holiday period must not exceed one year',
|
||||
path: ['endDate'],
|
||||
});
|
||||
export const schoolHolidayRegionRequestSchema = z.object({
|
||||
name: nonEmptyString.max(150),
|
||||
revision: z.number().int().nonnegative(),
|
||||
holidays: z.array(schoolHolidayPeriodSchema).max(500),
|
||||
});
|
||||
export const schoolHolidayRegionSchema = z.object({
|
||||
id: idSchema,
|
||||
country: z.string(),
|
||||
name: z.string(),
|
||||
code: z.string(),
|
||||
revision: z.number().int(),
|
||||
});
|
||||
export const schoolHolidayCatalogSchema = z.object({
|
||||
countries: z.array(schoolHolidayCountryRequestSchema),
|
||||
regions: z.array(schoolHolidayRegionSchema),
|
||||
});
|
||||
export const schoolHolidayRegionDetailSchema = schoolHolidayRegionSchema.extend({
|
||||
holidays: z.array(schoolHolidayPeriodSchema),
|
||||
});
|
||||
export type SchoolHolidayCountryRequest = z.infer<typeof schoolHolidayCountryRequestSchema>;
|
||||
export type SchoolHolidayPeriod = z.infer<typeof schoolHolidayPeriodSchema>;
|
||||
export type SchoolHolidayRegionRequest = z.infer<typeof schoolHolidayRegionRequestSchema>;
|
||||
export type SchoolHolidayRegion = z.infer<typeof schoolHolidayRegionSchema>;
|
||||
export type SchoolHolidayCatalog = z.infer<typeof schoolHolidayCatalogSchema>;
|
||||
export type SchoolHolidayRegionDetail = z.infer<typeof schoolHolidayRegionDetailSchema>;
|
||||
|
||||
export function manualSchoolRegionId(code: string): number | null {
|
||||
const match = /^[A-Z]{2}-MANUAL-([1-9][0-9]*)$/.exec(code);
|
||||
return match ? Number(match[1]) : null;
|
||||
}
|
||||
Reference in New Issue
Block a user