ExternalLinkPageTheme.test.tsx 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. /**
  2. * The external-link page tells the framed app which theme Bambuddy shows, so
  3. * an app made for the sidebar (Bambuddy Orders) can match it.
  4. */
  5. import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  6. import { fireEvent, screen, waitFor } from '@testing-library/react';
  7. import { Route, Routes } from 'react-router-dom';
  8. import { render } from '../utils';
  9. import { api } from '../../api/client';
  10. import { ExternalLinkPage } from '../../pages/ExternalLinkPage';
  11. const LINK = {
  12. id: 5,
  13. name: 'Orders',
  14. url: 'http://orders.local:8090/',
  15. icon: 'shopping-cart',
  16. open_in_new_tab: false,
  17. custom_icon: null,
  18. sort_order: 0,
  19. created_at: '2026-09-27T00:00:00Z',
  20. updated_at: '2026-09-27T00:00:00Z',
  21. };
  22. function renderPage() {
  23. window.history.pushState({}, '', '/external/5');
  24. return render(
  25. <Routes>
  26. <Route path="/external/:id" element={<ExternalLinkPage />} />
  27. </Routes>,
  28. );
  29. }
  30. beforeEach(() => {
  31. vi.spyOn(api, 'getExternalLink').mockResolvedValue(LINK);
  32. });
  33. afterEach(() => {
  34. vi.restoreAllMocks();
  35. });
  36. // The values come from the theme context (local choice plus the server
  37. // settings sync); the contract is the message's shape.
  38. const THEME = {
  39. type: 'bambuddy:theme',
  40. mode: expect.stringMatching(/^(light|dark)$/),
  41. style: expect.any(String),
  42. background: expect.any(String),
  43. accent: expect.any(String),
  44. canNavigate: true,
  45. };
  46. describe('ExternalLinkPage theme', () => {
  47. it('sends the theme to the link origin when the page loads', async () => {
  48. renderPage();
  49. const frame = (await screen.findByTitle('Orders')) as HTMLIFrameElement;
  50. const post = vi.spyOn(frame.contentWindow!, 'postMessage');
  51. fireEvent.load(frame);
  52. expect(post).toHaveBeenCalledWith(THEME, 'http://orders.local:8090');
  53. });
  54. it('answers when the framed page asks for the theme', async () => {
  55. renderPage();
  56. const frame = (await screen.findByTitle('Orders')) as HTMLIFrameElement;
  57. const post = vi.spyOn(frame.contentWindow!, 'postMessage');
  58. window.dispatchEvent(
  59. new MessageEvent('message', { data: { type: 'bambuddy:theme-request' }, source: frame.contentWindow }),
  60. );
  61. await waitFor(() => expect(post).toHaveBeenCalledWith(THEME, 'http://orders.local:8090'));
  62. });
  63. it('ignores theme requests from other windows', async () => {
  64. renderPage();
  65. const frame = (await screen.findByTitle('Orders')) as HTMLIFrameElement;
  66. const post = vi.spyOn(frame.contentWindow!, 'postMessage');
  67. window.dispatchEvent(new MessageEvent('message', { data: { type: 'bambuddy:theme-request' }, source: window }));
  68. expect(post).not.toHaveBeenCalled();
  69. });
  70. });