Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions tests/unit/web/jira-wizard-isComplete.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* JIRA wizard — isComplete predicates for optional steps.
*
* Guards that optional steps (labels, custom-fields, issue-types, webhook)
* only show green check marks after the required steps (credentials +
* project + status mapping) are all complete. Prevents the UI bug where a
* brand-new unconfigured integration showed every step as green.
*/

import { describe, expect, it } from 'vitest';
import { jiraProviderWizard } from '../../../web/src/components/projects/pm-providers/jira/wizard.js';
import { createInitialState } from '../../../web/src/components/projects/pm-wizard-state.js';

const getStep = (id: string) => {
const step = jiraProviderWizard.steps.find((s) => s.id === id);
if (!step) throw new Error(`Step ${id} not found`);
return step;
};

describe('JIRA optional steps — isComplete gating', () => {
describe('fresh state (createInitialState)', () => {
const state = createInitialState();

it('jira-labels is NOT complete on fresh state', () => {
expect(getStep('jira-labels').isComplete(state)).toBe(false);
});

it('jira-custom-fields is NOT complete on fresh state', () => {
expect(getStep('jira-custom-fields').isComplete(state)).toBe(false);
});

it('jira-issue-types is NOT complete on fresh state', () => {
expect(getStep('jira-issue-types').isComplete(state)).toBe(false);
});

it('jira-webhook is NOT complete on fresh state', () => {
expect(getStep('jira-webhook').isComplete(state)).toBe(false);
});
});

describe('partially configured (credentials only, no project)', () => {
const state = {
...createInitialState(),
jiraEmail: 'user@example.com',
jiraApiToken: 'token123',
jiraBaseUrl: 'https://example.atlassian.net',
verificationResult: { provider: 'jira' as const, display: 'user@example.com' },
};

it('jira-labels is NOT complete when project not selected', () => {
expect(getStep('jira-labels').isComplete(state)).toBe(false);
});

it('jira-custom-fields is NOT complete when project not selected', () => {
expect(getStep('jira-custom-fields').isComplete(state)).toBe(false);
});

it('jira-issue-types is NOT complete when project not selected', () => {
expect(getStep('jira-issue-types').isComplete(state)).toBe(false);
});

it('jira-webhook is NOT complete when project not selected', () => {
expect(getStep('jira-webhook').isComplete(state)).toBe(false);
});
});

describe('credentials + project, but no status mapping', () => {
const state = {
...createInitialState(),
jiraEmail: 'user@example.com',
jiraApiToken: 'token123',
jiraBaseUrl: 'https://example.atlassian.net',
verificationResult: { provider: 'jira' as const, display: 'user@example.com' },
jiraProjectKey: 'PROJ',
jiraStatusMappings: {},
};

it('jira-labels is NOT complete without status mapping', () => {
expect(getStep('jira-labels').isComplete(state)).toBe(false);
});

it('jira-webhook is NOT complete without status mapping', () => {
expect(getStep('jira-webhook').isComplete(state)).toBe(false);
});
});

describe('fully configured (credentials + project + status mapping)', () => {
const state = {
...createInitialState(),
jiraEmail: 'user@example.com',
jiraApiToken: 'token123',
jiraBaseUrl: 'https://example.atlassian.net',
verificationResult: { provider: 'jira' as const, display: 'user@example.com' },
jiraProjectKey: 'PROJ',
jiraStatusMappings: { todo: 'To Do', inProgress: 'In Progress' },
};

it('jira-labels is complete when all required steps done', () => {
expect(getStep('jira-labels').isComplete(state)).toBe(true);
});

it('jira-custom-fields is complete when all required steps done', () => {
expect(getStep('jira-custom-fields').isComplete(state)).toBe(true);
});

it('jira-issue-types is complete when all required steps done', () => {
expect(getStep('jira-issue-types').isComplete(state)).toBe(true);
});

it('jira-webhook is complete when all required steps done', () => {
expect(getStep('jira-webhook').isComplete(state)).toBe(true);
});
});

describe('edit mode with stored credentials (isEditing + hasStoredCredentials)', () => {
const state = {
...createInitialState(),
isEditing: true,
hasStoredCredentials: true,
jiraProjectKey: 'PROJ',
jiraStatusMappings: { todo: 'To Do' },
};

it('jira-labels is complete in edit mode with stored credentials', () => {
expect(getStep('jira-labels').isComplete(state)).toBe(true);
});

it('jira-custom-fields is complete in edit mode with stored credentials', () => {
expect(getStep('jira-custom-fields').isComplete(state)).toBe(true);
});

it('jira-issue-types is complete in edit mode with stored credentials', () => {
expect(getStep('jira-issue-types').isComplete(state)).toBe(true);
});

it('jira-webhook is complete in edit mode with stored credentials', () => {
expect(getStep('jira-webhook').isComplete(state)).toBe(true);
});
});
});
122 changes: 122 additions & 0 deletions tests/unit/web/linear-wizard-isComplete.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Linear wizard — isComplete predicates for optional steps.
*
* Guards that optional steps (labels, project-scope, webhook) only show
* green check marks after the required steps (credentials + team + status
* mapping) are all complete. Prevents the UI bug where a brand-new
* unconfigured integration showed every step as green.
*/

import { describe, expect, it } from 'vitest';
import { linearProviderWizard } from '../../../web/src/components/projects/pm-providers/linear/wizard.js';
import { createInitialState } from '../../../web/src/components/projects/pm-wizard-state.js';

const getStep = (id: string) => {
const step = linearProviderWizard.steps.find((s) => s.id === id);
if (!step) throw new Error(`Step ${id} not found`);
return step;
};

describe('Linear optional steps — isComplete gating', () => {
describe('fresh state (createInitialState)', () => {
const state = createInitialState();

it('linear-labels is NOT complete on fresh state', () => {
expect(getStep('linear-labels').isComplete(state)).toBe(false);
});

it('linear-project-scope is NOT complete on fresh state', () => {
expect(getStep('linear-project-scope').isComplete(state)).toBe(false);
});

it('linear-webhook is NOT complete on fresh state', () => {
expect(getStep('linear-webhook').isComplete(state)).toBe(false);
});
});

describe('partially configured (credentials only, no team)', () => {
const state = {
...createInitialState(),
linearApiKey: 'lin_api_123',
verificationResult: { provider: 'linear' as const, display: 'user@example.com' },
};

it('linear-labels is NOT complete when team not selected', () => {
expect(getStep('linear-labels').isComplete(state)).toBe(false);
});

it('linear-project-scope is NOT complete when team not selected', () => {
expect(getStep('linear-project-scope').isComplete(state)).toBe(false);
});

it('linear-webhook is NOT complete when team not selected', () => {
expect(getStep('linear-webhook').isComplete(state)).toBe(false);
});
});

describe('credentials + team, but no status mapping', () => {
const state = {
...createInitialState(),
linearApiKey: 'lin_api_123',
verificationResult: { provider: 'linear' as const, display: 'user@example.com' },
linearTeamId: 'team-1',
linearStatusMappings: {},
};

it('linear-labels is NOT complete without status mapping', () => {
expect(getStep('linear-labels').isComplete(state)).toBe(false);
});

it('linear-project-scope is NOT complete without status mapping', () => {
expect(getStep('linear-project-scope').isComplete(state)).toBe(false);
});

it('linear-webhook is NOT complete without status mapping', () => {
expect(getStep('linear-webhook').isComplete(state)).toBe(false);
});
});

describe('fully configured (credentials + team + status mapping)', () => {
const state = {
...createInitialState(),
linearApiKey: 'lin_api_123',
verificationResult: { provider: 'linear' as const, display: 'user@example.com' },
linearTeamId: 'team-1',
linearStatusMappings: { todo: 'state-uuid-1', inProgress: 'state-uuid-2' },
};

it('linear-labels is complete when all required steps done', () => {
expect(getStep('linear-labels').isComplete(state)).toBe(true);
});

it('linear-project-scope is complete when all required steps done', () => {
expect(getStep('linear-project-scope').isComplete(state)).toBe(true);
});

it('linear-webhook is complete when all required steps done', () => {
expect(getStep('linear-webhook').isComplete(state)).toBe(true);
});
});

describe('edit mode with stored credentials (isEditing + hasStoredCredentials)', () => {
const state = {
...createInitialState(),
isEditing: true,
hasStoredCredentials: true,
linearTeamId: 'team-1',
linearStatusMappings: { todo: 'state-uuid-1' },
};

it('linear-labels is complete in edit mode with stored credentials', () => {
expect(getStep('linear-labels').isComplete(state)).toBe(true);
});

it('linear-project-scope is complete in edit mode with stored credentials', () => {
expect(getStep('linear-project-scope').isComplete(state)).toBe(true);
});

it('linear-webhook is complete in edit mode with stored credentials', () => {
expect(getStep('linear-webhook').isComplete(state)).toBe(true);
});
});
});
122 changes: 122 additions & 0 deletions tests/unit/web/trello-wizard-isComplete.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Trello wizard — isComplete predicates for optional steps.
*
* Guards that optional steps (labels, custom-fields, webhook) only show
* green check marks after the required steps (credentials + board + status
* mapping) are all complete. Prevents the UI bug where a brand-new
* unconfigured integration showed every step as green.
*/

import { describe, expect, it } from 'vitest';
import { trelloProviderWizard } from '../../../web/src/components/projects/pm-providers/trello/wizard.js';
import { createInitialState } from '../../../web/src/components/projects/pm-wizard-state.js';

// Grab the optional steps by id
const getStep = (id: string) => {
const step = trelloProviderWizard.steps.find((s) => s.id === id);
if (!step) throw new Error(`Step ${id} not found`);
return step;
};

describe('Trello optional steps — isComplete gating', () => {
describe('fresh state (createInitialState)', () => {
const state = createInitialState();

it('trello-labels is NOT complete on fresh state', () => {
expect(getStep('trello-labels').isComplete(state)).toBe(false);
});

it('trello-custom-fields is NOT complete on fresh state', () => {
expect(getStep('trello-custom-fields').isComplete(state)).toBe(false);
});

it('trello-webhook is NOT complete on fresh state', () => {
expect(getStep('trello-webhook').isComplete(state)).toBe(false);
});
});

describe('partially configured (credentials only, no board)', () => {
const state = {
...createInitialState(),
trelloApiKey: 'key123',
trelloToken: 'token123',
verificationResult: { provider: 'trello' as const, display: 'user@example.com' },
};

it('trello-labels is NOT complete when board not selected', () => {
expect(getStep('trello-labels').isComplete(state)).toBe(false);
});

it('trello-custom-fields is NOT complete when board not selected', () => {
expect(getStep('trello-custom-fields').isComplete(state)).toBe(false);
});

it('trello-webhook is NOT complete when board not selected', () => {
expect(getStep('trello-webhook').isComplete(state)).toBe(false);
});
});

describe('credentials + board, but no status mapping', () => {
const state = {
...createInitialState(),
trelloApiKey: 'key123',
trelloToken: 'token123',
verificationResult: { provider: 'trello' as const, display: 'user@example.com' },
trelloBoardId: 'board-1',
trelloListMappings: {},
};

it('trello-labels is NOT complete without status mapping', () => {
expect(getStep('trello-labels').isComplete(state)).toBe(false);
});

it('trello-webhook is NOT complete without status mapping', () => {
expect(getStep('trello-webhook').isComplete(state)).toBe(false);
});
});

describe('fully configured (credentials + board + status mapping)', () => {
const state = {
...createInitialState(),
trelloApiKey: 'key123',
trelloToken: 'token123',
verificationResult: { provider: 'trello' as const, display: 'user@example.com' },
trelloBoardId: 'board-1',
trelloListMappings: { todo: 'list-1', inProgress: 'list-2' },
};

it('trello-labels is complete when all required steps done', () => {
expect(getStep('trello-labels').isComplete(state)).toBe(true);
});

it('trello-custom-fields is complete when all required steps done', () => {
expect(getStep('trello-custom-fields').isComplete(state)).toBe(true);
});

it('trello-webhook is complete when all required steps done', () => {
expect(getStep('trello-webhook').isComplete(state)).toBe(true);
});
});

describe('edit mode with stored credentials (isEditing + hasStoredCredentials)', () => {
const state = {
...createInitialState(),
isEditing: true,
hasStoredCredentials: true,
trelloBoardId: 'board-1',
trelloListMappings: { todo: 'list-1' },
};

it('trello-labels is complete in edit mode with stored credentials', () => {
expect(getStep('trello-labels').isComplete(state)).toBe(true);
});

it('trello-custom-fields is complete in edit mode with stored credentials', () => {
expect(getStep('trello-custom-fields').isComplete(state)).toBe(true);
});

it('trello-webhook is complete in edit mode with stored credentials', () => {
expect(getStep('trello-webhook').isComplete(state)).toBe(true);
});
});
});
Loading
Loading