assignableProjects.test.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. /**
  2. * Which projects a picker offers when filing something away (#2888).
  3. *
  4. * The reporter had five active projects sitting behind thirty-odd finished
  5. * ones, and every dropdown that assigns an archive listed all of them. This is
  6. * the rule those pickers now share: archived out, completed in, and whatever
  7. * the thing being edited already belongs to stays regardless.
  8. */
  9. import { describe, it, expect } from 'vitest';
  10. import { assignableProjects } from '../../utils/projectTree';
  11. import type { ProjectListItem } from '../../api/client';
  12. const project = (over: Partial<ProjectListItem>): ProjectListItem => ({
  13. id: 1,
  14. name: 'Airframe',
  15. description: null,
  16. color: '#00ae42',
  17. status: 'active',
  18. target_count: null,
  19. target_parts_count: null,
  20. target_sets: null,
  21. budget: null,
  22. tags: null,
  23. due_date: null,
  24. priority: 'normal',
  25. created_at: '2026-01-01T00:00:00Z',
  26. archive_count: 0,
  27. total_items: 0,
  28. completed_count: 0,
  29. failed_count: 0,
  30. queue_count: 0,
  31. progress_percent: null,
  32. parent_id: null,
  33. child_count: 0,
  34. archives: [],
  35. url: null,
  36. cover_image_filename: null,
  37. ...over,
  38. });
  39. const ids = (rows: ProjectListItem[]) => rows.map((p) => p.id);
  40. describe('assignableProjects', () => {
  41. it('drops archived projects', () => {
  42. const projects = [
  43. project({ id: 1, status: 'active' }),
  44. project({ id: 2, status: 'archived' }),
  45. ];
  46. expect(ids(assignableProjects(projects))).toEqual([1]);
  47. });
  48. it('keeps completed projects', () => {
  49. // Filing a reprint against a finished project is ordinary work --
  50. // "completed" says the job is done, not that it should be hidden.
  51. const projects = [
  52. project({ id: 1, status: 'active' }),
  53. project({ id: 2, status: 'completed' }),
  54. project({ id: 3, status: 'archived' }),
  55. ];
  56. expect(ids(assignableProjects(projects))).toEqual([1, 2]);
  57. });
  58. it('keeps the archived project the caller names', () => {
  59. // A controlled <select> holding a value no option matches is reset by the
  60. // browser to its first option -- "No project" -- so an archive filed in an
  61. // archived project would claim to be filed nowhere.
  62. const projects = [
  63. project({ id: 1, status: 'active' }),
  64. project({ id: 2, status: 'archived' }),
  65. ];
  66. expect(ids(assignableProjects(projects, 2))).toEqual([1, 2]);
  67. });
  68. it('keeps only the named archived project, not archived ones generally', () => {
  69. const projects = [
  70. project({ id: 2, status: 'archived' }),
  71. project({ id: 3, status: 'archived' }),
  72. ];
  73. expect(ids(assignableProjects(projects, 3))).toEqual([3]);
  74. });
  75. it('treats no current project as naming nothing', () => {
  76. // The pickers pass `archive.project_id` straight through, and an
  77. // unassigned archive carries null there. Reading null as an id would be
  78. // harmless today only because no project has that id -- but a picker with
  79. // no argument at all must behave the same way.
  80. const projects = [project({ id: 1, status: 'archived' })];
  81. expect(assignableProjects(projects, null)).toEqual([]);
  82. expect(assignableProjects(projects, undefined)).toEqual([]);
  83. expect(assignableProjects(projects)).toEqual([]);
  84. });
  85. it('leaves the order it was given', () => {
  86. // Every caller sorts by name either side of this, so reordering here
  87. // would silently fight them.
  88. const projects = [
  89. project({ id: 3, name: 'Wing' }),
  90. project({ id: 1, name: 'Airframe' }),
  91. project({ id: 2, name: 'Spar' }),
  92. ];
  93. expect(ids(assignableProjects(projects))).toEqual([3, 1, 2]);
  94. });
  95. it('does not modify the array it is given', () => {
  96. const projects = [
  97. project({ id: 1, status: 'active' }),
  98. project({ id: 2, status: 'archived' }),
  99. ];
  100. assignableProjects(projects);
  101. expect(ids(projects)).toEqual([1, 2]);
  102. });
  103. });