projectQueries.test.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. /**
  2. * Tests for the project-view cache invalidation helper (#2731).
  3. *
  4. * The default staleTime is 60s, so a project page revisited within a minute of
  5. * deleting one of its prints serves the cached answer and keeps showing the
  6. * print. The user had to reload the page by hand. Deletes invalidated only
  7. * `['archives']`; the project-assign mutations only `['projects']`, which
  8. * refreshed the overview cards but never the detail page.
  9. */
  10. import { describe, it, expect, vi } from 'vitest';
  11. import type { QueryClient } from '@tanstack/react-query';
  12. import { invalidateProjectViews, invalidateArchiveAndProjectViews } from '../../utils/projectQueries';
  13. function mockClient() {
  14. const invalidateQueries = vi.fn().mockResolvedValue(undefined);
  15. return { client: { invalidateQueries } as unknown as QueryClient, invalidateQueries };
  16. }
  17. const keysFrom = (fn: ReturnType<typeof vi.fn>) => fn.mock.calls.map((c) => c[0].queryKey.join('/'));
  18. describe('invalidateProjectViews', () => {
  19. it('refreshes every view whose contents depend on project membership', async () => {
  20. const { client, invalidateQueries } = mockClient();
  21. await invalidateProjectViews(client);
  22. expect(keysFrom(invalidateQueries).sort()).toEqual(
  23. ['project', 'project-archives', 'project-file-progress', 'project-timeline', 'projects'].sort(),
  24. );
  25. });
  26. it('uses bare prefixes so every cached project id is covered', async () => {
  27. const { client, invalidateQueries } = mockClient();
  28. await invalidateProjectViews(client);
  29. // ['project'] matches ['project', 42]; ['project', 42] would not match 43.
  30. for (const call of invalidateQueries.mock.calls) {
  31. expect(call[0].queryKey).toHaveLength(1);
  32. }
  33. });
  34. it('does not touch the archive list on its own', async () => {
  35. const { client, invalidateQueries } = mockClient();
  36. await invalidateProjectViews(client);
  37. expect(keysFrom(invalidateQueries)).not.toContain('archives');
  38. });
  39. });
  40. describe('invalidateArchiveAndProjectViews', () => {
  41. it('adds the archive list to the project views', async () => {
  42. const { client, invalidateQueries } = mockClient();
  43. await invalidateArchiveAndProjectViews(client);
  44. const keys = keysFrom(invalidateQueries);
  45. expect(keys).toContain('archives');
  46. expect(keys).toContain('project-archives');
  47. expect(keys).toContain('projects');
  48. });
  49. it('resolves only once every invalidation has settled', async () => {
  50. let settled = 0;
  51. const invalidateQueries = vi.fn().mockImplementation(
  52. () => new Promise<void>((resolve) => setTimeout(() => { settled += 1; resolve(); }, 0)),
  53. );
  54. await invalidateArchiveAndProjectViews({ invalidateQueries } as unknown as QueryClient);
  55. expect(settled).toBe(6);
  56. });
  57. });