2026-07-12 12:28:38 -07:00
import { describe , test , expect } from 'vitest' ;
import { buildSavePayload } from './useWhpApi' ;
import { PageData } from '../types' ;
2026-07-12 18:22:38 -07:00
import { DEFAULT_SITE_DESIGN } from '../state/SiteDesignContext' ;
2026-07-12 12:28:38 -07:00
2026-07-12 13:30:17 -07:00
const pageA : PageData = { id : 'home' , name : 'Home' , slug : 'index' , craftState : 'STORED_HOME' };
const pageB : PageData = { id : 'page_2' , name : 'About' , slug : 'about' , craftState : 'STORED_ABOUT' };
const headerPage : PageData = { id : '__header__' , name : 'Header' , slug : '__header__' , craftState : 'STALE_HEADER' };
const footerPage : PageData = { id : '__footer__' , name : 'Footer' , slug : '__footer__' , craftState : 'STALE_FOOTER' };
2026-07-12 12:28:38 -07:00
describe ( 'buildSavePayload' , () => {
test ( 'editing header: live serialize lands in header_craft_state, not in any page slot' , () => {
const payload = buildSavePayload ({
siteId : 1 ,
siteName : 'Test Site' ,
liveCraftState : 'LIVE_HEADER' ,
pages : [ pageA , pageB ],
headerPage ,
footerPage ,
activePageId : '__header__' ,
isEditingHeader : true ,
isEditingFooter : false ,
2026-07-12 18:22:38 -07:00
headCode : DEFAULT_SITE_DESIGN.headCode ,
design : DEFAULT_SITE_DESIGN ,
2026-07-12 12:28:38 -07:00
});
// The fresh live canvas must be reflected in header_craft_state, not the stale stored one.
expect ( payload . header_craft_state ). toBe ( 'LIVE_HEADER' );
expect ( payload . header_craft_state ). not . toBe ( 'STALE_HEADER' );
// Footer must remain untouched (stored state, since we're not editing it).
expect ( payload . footer_craft_state ). toBe ( 'STALE_FOOTER' );
// Live header content must never leak into a page slot or the top-level page fields.
expect ( payload . craft_state ). not . toBe ( 'LIVE_HEADER' );
for ( const p of payload . pages_craft_state ) {
expect ( p . craftState ). not . toBe ( 'LIVE_HEADER' );
}
// Top-level page fields should fall back to the landing page's own stored state.
expect ( payload . craft_state ). toBe ( 'STORED_HOME' );
// Per-page slots must reflect each page's own stored state, untouched.
expect ( payload . pages_craft_state . find (( p ) => p . id === 'home' ) ? . craftState ). toBe ( 'STORED_HOME' );
expect ( payload . pages_craft_state . find (( p ) => p . id === 'page_2' ) ? . craftState ). toBe ( 'STORED_ABOUT' );
});
test ( 'editing footer: live serialize lands in footer_craft_state, not in any page slot' , () => {
const payload = buildSavePayload ({
siteId : 1 ,
siteName : 'Test Site' ,
liveCraftState : 'LIVE_FOOTER' ,
pages : [ pageA , pageB ],
headerPage ,
footerPage ,
activePageId : '__footer__' ,
isEditingHeader : false ,
isEditingFooter : true ,
2026-07-12 18:22:38 -07:00
headCode : DEFAULT_SITE_DESIGN.headCode ,
design : DEFAULT_SITE_DESIGN ,
2026-07-12 12:28:38 -07:00
});
expect ( payload . footer_craft_state ). toBe ( 'LIVE_FOOTER' );
expect ( payload . footer_craft_state ). not . toBe ( 'STALE_FOOTER' );
expect ( payload . header_craft_state ). toBe ( 'STALE_HEADER' );
expect ( payload . craft_state ). not . toBe ( 'LIVE_FOOTER' );
for ( const p of payload . pages_craft_state ) {
expect ( p . craftState ). not . toBe ( 'LIVE_FOOTER' );
}
});
test ( 'editing a real page: behavior unchanged — live serialize goes to that page + top-level, header/footer come from stored state' , () => {
const payload = buildSavePayload ({
siteId : 1 ,
siteName : 'Test Site' ,
liveCraftState : 'LIVE_PAGE_HOME' ,
pages : [ pageA , pageB ],
headerPage ,
footerPage ,
activePageId : 'home' ,
isEditingHeader : false ,
isEditingFooter : false ,
2026-07-12 18:22:38 -07:00
headCode : DEFAULT_SITE_DESIGN.headCode ,
design : DEFAULT_SITE_DESIGN ,
2026-07-12 12:28:38 -07:00
});
// Live canvas goes to the active page and top-level slots.
expect ( payload . craft_state ). toBe ( 'LIVE_PAGE_HOME' );
expect ( payload . pages_craft_state . find (( p ) => p . id === 'home' ) ? . craftState ). toBe ( 'LIVE_PAGE_HOME' );
// Other pages keep their stored state.
expect ( payload . pages_craft_state . find (( p ) => p . id === 'page_2' ) ? . craftState ). toBe ( 'STORED_ABOUT' );
// Header/footer come from stored (stale-but-correct, since we're not editing them) state.
expect ( payload . header_craft_state ). toBe ( 'STALE_HEADER' );
expect ( payload . footer_craft_state ). toBe ( 'STALE_FOOTER' );
});
2026-07-12 17:29:32 -07:00
test ( 'I-1: activePageId matches no page (dangling, e.g. original Home deleted then reload reset it to "home") -- live serialize still lands in pages_craft_state[0] / index.html, not lost' , () => {
// pages[0] has id 'page_x' (the replacement landing page after the
// original 'home' was deleted); activePageId is stuck at the stale
// default 'home', which matches no entry in `pages`.
const pageX : PageData = { id : 'page_x' , name : 'Home' , slug : 'index' , craftState : 'STORED_X' };
const p2 : PageData = { id : 'p2' , name : 'About' , slug : 'about' , craftState : 'STORED_ABOUT_2' };
const payload = buildSavePayload ({
siteId : 1 ,
siteName : 'Test Site' ,
liveCraftState : 'LIVE_EDIT' ,
pages : [ pageX , p2 ],
headerPage ,
footerPage ,
activePageId : 'home' ,
isEditingHeader : false ,
isEditingFooter : false ,
2026-07-12 18:22:38 -07:00
headCode : DEFAULT_SITE_DESIGN.headCode ,
design : DEFAULT_SITE_DESIGN ,
2026-07-12 17:29:32 -07:00
});
// The live edit must land in pages_craft_state[0] (index.html slot),
// not be silently dropped to only the legacy top-level fields.
expect ( payload . pages_craft_state [ 0 ]. craftState ). toBe ( 'LIVE_EDIT' );
expect ( payload . pages [ 0 ]. filename ). toBe ( 'index.html' );
// Top-level fields (legacy) should also reflect the live edit.
expect ( payload . craft_state ). toBe ( 'LIVE_EDIT' );
// The other page is untouched.
expect ( payload . pages_craft_state . find (( p ) => p . id === 'p2' ) ? . craftState ). toBe ( 'STORED_ABOUT_2' );
});
2026-07-12 18:22:38 -07:00
test ( 'head code + design tokens are included in the save payload' , () => {
const design = { ... DEFAULT_SITE_DESIGN , headCode : '<meta name="x">' };
const payload = buildSavePayload ({
siteId : 1 ,
siteName : 'Test Site' ,
liveCraftState : 'LIVE_PAGE_HOME' ,
pages : [ pageA , pageB ],
headerPage ,
footerPage ,
activePageId : 'home' ,
isEditingHeader : false ,
isEditingFooter : false ,
headCode : '<meta name="x">' ,
design ,
});
expect ( payload . head_code ). toBe ( '<meta name="x">' );
expect ( payload . design ). toEqual ( design );
expect ( payload . design . headCode ). toBe ( '<meta name="x">' );
});
2026-07-12 12:28:38 -07:00
});