| 1 | import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 2 | |
| 3 | const state = vi.hoisted(() => ({ |
| 4 | handlers: new Map<string, (...args: unknown[]) => unknown>(), |
| 5 | getStatus: vi.fn(), |
| 6 | getLayoutLibraryStatus: vi.fn(), |
| 7 | saveLayoutLibrary: vi.fn(), |
| 8 | save: vi.fn(), |
| 9 | setPageOverride: vi.fn() |
| 10 | })) |
| 11 | |
| 12 | vi.mock('electron', () => ({ |
| 13 | ipcMain: { |
| 14 | handle: (name: string, handler: (...args: unknown[]) => unknown) => |
| 15 | state.handlers.set(name, handler) |
| 16 | } |
| 17 | })) |
| 18 | |
| 19 | vi.mock('../../../src/main/session/master-mutation-service', () => ({ |
| 20 | getSessionMasterStatus: state.getStatus, |
| 21 | getSessionLayoutLibraryStatus: state.getLayoutLibraryStatus, |
| 22 | saveSessionLayoutLibrary: state.saveLayoutLibrary, |
| 23 | saveSessionMaster: state.save, |
| 24 | setSessionMasterPageOverride: state.setPageOverride |
| 25 | })) |
| 26 | |
| 27 | describe('registerMasterHandlers', () => { |
| 28 | beforeEach(() => { |
| 29 | vi.resetModules() |
| 30 | state.handlers.clear() |
| 31 | state.getStatus.mockReset() |
| 32 | state.getLayoutLibraryStatus.mockReset() |
| 33 | state.saveLayoutLibrary.mockReset() |
| 34 | state.save.mockReset() |
| 35 | state.setPageOverride.mockReset() |
| 36 | }) |
| 37 | |
| 38 | it('registers typed handlers and rejects malformed structured config', async () => { |
| 39 | const { registerMasterHandlers } = await import('../../../src/main/session/master-handlers') |
| 40 | const ctx = {} as never |
| 41 | registerMasterHandlers(ctx) |
| 42 | |
| 43 | const get = state.handlers.get('session:getMaster') |
| 44 | const save = state.handlers.get('session:saveMaster') |
| 45 | const setPageOverride = state.handlers.get('session:setMasterPageOverride') |
| 46 | const getLayoutLibrary = state.handlers.get('session:getLayoutLibrary') |
| 47 | const saveLayoutLibrary = state.handlers.get('session:saveLayoutLibrary') |
| 48 | expect(get).toBeTypeOf('function') |
| 49 | expect(save).toBeTypeOf('function') |
| 50 | expect(setPageOverride).toBeTypeOf('function') |
| 51 | expect(getLayoutLibrary).toBeTypeOf('function') |
| 52 | expect(saveLayoutLibrary).toBeTypeOf('function') |
| 53 | expect(state.handlers.has('session:applyMasterToPages')).toBe(false) |
| 54 | |
| 55 | await get?.({}, { sessionId: ' session-1 ' }) |
| 56 | expect(state.getStatus).toHaveBeenCalledWith(ctx, 'session-1') |
| 57 | |
| 58 | await expect( |
| 59 | save?.({}, { sessionId: 'session-1', config: { backgroundColor: 'red' } }) |
| 60 | ).rejects.toThrow('母版配置无效') |
| 61 | await save?.( |
| 62 | {}, |
| 63 | { |
| 64 | sessionId: 'session-1', |
| 65 | config: { |
| 66 | backgroundColor: '#112233', |
| 67 | backgroundMode: 'override', |
| 68 | backgroundStyle: 'gradient', |
| 69 | backgroundGradient: { |
| 70 | type: 'linear', |
| 71 | angle: 135, |
| 72 | stops: [ |
| 73 | { color: '#112233', position: 0 }, |
| 74 | { color: '#ffffff', position: 100 } |
| 75 | ] |
| 76 | }, |
| 77 | backgroundImage: null, |
| 78 | titleFontPreset: 'serif', |
| 79 | bodyFontPreset: 'sans', |
| 80 | titleFontFamily: 'Noto Sans SC', |
| 81 | bodyFontFamily: null, |
| 82 | titleFontSize: 56, |
| 83 | bodyFontSize: null, |
| 84 | elements: { |
| 85 | logoImage: null, |
| 86 | footerText: '', |
| 87 | watermarkText: '', |
| 88 | showPageNumber: true |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | ) |
| 93 | expect(state.save).toHaveBeenCalledWith( |
| 94 | ctx, |
| 95 | 'session-1', |
| 96 | expect.objectContaining({ backgroundMode: 'override' }) |
| 97 | ) |
| 98 | |
| 99 | await save?.( |
| 100 | {}, |
| 101 | { |
| 102 | sessionId: 'session-1', |
| 103 | config: { |
| 104 | backgroundColor: '#ffffff', |
| 105 | backgroundMode: 'override', |
| 106 | backgroundStyle: 'image', |
| 107 | backgroundGradient: { |
| 108 | type: 'linear', |
| 109 | angle: 135, |
| 110 | stops: [ |
| 111 | { color: '#112233', position: 0 }, |
| 112 | { color: '#ffffff', position: 100 } |
| 113 | ] |
| 114 | }, |
| 115 | backgroundImage: './images/master-background.png', |
| 116 | titleFontPreset: 'inherit', |
| 117 | bodyFontPreset: 'inherit', |
| 118 | titleFontFamily: null, |
| 119 | bodyFontFamily: null, |
| 120 | titleFontSize: null, |
| 121 | bodyFontSize: null, |
| 122 | elements: { |
| 123 | logoImage: './images/logo.png', |
| 124 | footerText: 'Acme', |
| 125 | watermarkText: '', |
| 126 | showPageNumber: true |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | ) |
| 131 | expect(state.save).toHaveBeenLastCalledWith( |
| 132 | ctx, |
| 133 | 'session-1', |
| 134 | expect.objectContaining({ |
| 135 | backgroundStyle: 'image', |
| 136 | backgroundImage: './images/master-background.png' |
| 137 | }) |
| 138 | ) |
| 139 | |
| 140 | await expect( |
| 141 | save?.( |
| 142 | {}, |
| 143 | { |
| 144 | sessionId: 'session-1', |
| 145 | config: { |
| 146 | backgroundColor: '#ffffff', |
| 147 | backgroundMode: 'override', |
| 148 | backgroundStyle: 'image', |
| 149 | backgroundGradient: { |
| 150 | type: 'linear', |
| 151 | angle: 135, |
| 152 | stops: [ |
| 153 | { color: '#112233', position: 0 }, |
| 154 | { color: '#ffffff', position: 100 } |
| 155 | ] |
| 156 | }, |
| 157 | backgroundImage: '../outside.png', |
| 158 | titleFontPreset: 'inherit', |
| 159 | bodyFontPreset: 'inherit', |
| 160 | titleFontFamily: null, |
| 161 | bodyFontFamily: null, |
| 162 | titleFontSize: null, |
| 163 | bodyFontSize: null, |
| 164 | elements: { |
| 165 | logoImage: null, |
| 166 | footerText: '', |
| 167 | watermarkText: '', |
| 168 | showPageNumber: true |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | ) |
| 173 | ).rejects.toThrow('母版配置无效') |
| 174 | |
| 175 | await expect( |
| 176 | save?.( |
| 177 | {}, |
| 178 | { |
| 179 | sessionId: 'session-1', |
| 180 | config: { |
| 181 | backgroundColor: '#ffffff', |
| 182 | backgroundMode: 'inherit', |
| 183 | backgroundStyle: 'solid', |
| 184 | backgroundGradient: { |
| 185 | type: 'linear', |
| 186 | angle: 135, |
| 187 | stops: [ |
| 188 | { color: '#112233', position: 0 }, |
| 189 | { color: '#ffffff', position: 100 } |
| 190 | ] |
| 191 | }, |
| 192 | backgroundImage: null, |
| 193 | titleFontPreset: 'inherit', |
| 194 | bodyFontPreset: 'inherit', |
| 195 | titleFontFamily: null, |
| 196 | bodyFontFamily: null, |
| 197 | titleFontSize: null, |
| 198 | bodyFontSize: null, |
| 199 | elements: { |
| 200 | logoImage: null, |
| 201 | footerText: '', |
| 202 | watermarkText: '', |
| 203 | showPageNumber: true, |
| 204 | logoPosition: { x: 101, y: 8 } |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | ) |
| 209 | ).rejects.toThrow('母版配置无效') |
| 210 | |
| 211 | await setPageOverride?.({}, { sessionId: 'session-1', pageId: 'page-row-1', disabled: true }) |
| 212 | expect(state.setPageOverride).toHaveBeenCalledWith(ctx, 'session-1', 'page-row-1', true) |
| 213 | await expect( |
| 214 | setPageOverride?.({}, { sessionId: 'session-1', pageId: '', disabled: true }) |
| 215 | ).rejects.toThrow('页面母版设置无效') |
| 216 | |
| 217 | await getLayoutLibrary?.({}, { sessionId: ' session-1 ' }) |
| 218 | expect(state.getLayoutLibraryStatus).toHaveBeenCalledWith(ctx, 'session-1') |
| 219 | await expect( |
| 220 | saveLayoutLibrary?.({}, { sessionId: 'session-1', library: { version: 1, mappings: {} } }) |
| 221 | ).rejects.toThrow('版式母版配置无效') |
| 222 | await saveLayoutLibrary?.( |
| 223 | {}, |
| 224 | { |
| 225 | sessionId: 'session-1', |
| 226 | library: { |
| 227 | version: 1, |
| 228 | mappings: { |
| 229 | cover: 'cover-split', |
| 230 | 'data-focus': 'data-metrics', |
| 231 | comparison: 'comparison-versus', |
| 232 | timeline: 'timeline-progress', |
| 233 | concept: 'content-editorial', |
| 234 | process: 'process-flow', |
| 235 | summary: 'summary-takeaway', |
| 236 | quote: 'quote-focus', |
| 237 | 'image-focus': 'image-spotlight' |
| 238 | } |
| 239 | } |
| 240 | } |
| 241 | ) |
| 242 | expect(state.saveLayoutLibrary).toHaveBeenCalledWith( |
| 243 | ctx, |
| 244 | 'session-1', |
| 245 | expect.objectContaining({ mappings: expect.objectContaining({ cover: 'cover-split' }) }) |
| 246 | ) |
| 247 | }) |
| 248 | }) |
| 249 |