Adding Organiztion, environment and project. Segment fix, tag fix
This commit is contained in:
@@ -15,6 +15,8 @@ jest.mock('@/api/client', () => ({
|
||||
|
||||
import * as envApi from '@/api/environments';
|
||||
|
||||
const dialogStub = { template: '<div><slot /></div>' };
|
||||
|
||||
const mockProject: ProjectResponse = {
|
||||
id: 10,
|
||||
name: 'Website',
|
||||
@@ -33,7 +35,10 @@ function mountComponent() {
|
||||
routes: [{ path: '/', component: { template: '<div />' } }],
|
||||
});
|
||||
return mount(EnvironmentTabBar, {
|
||||
global: { plugins: [router] },
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: { 'v-dialog': dialogStub },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,6 +62,13 @@ describe('EnvironmentTabBar', () => {
|
||||
|
||||
expect(wrapper.find('[data-testid="env-no-project-message"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenCreateEnvironmentButtonIsDisabled', async () => {
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="create-env-btn"]').attributes('disabled')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenProjectIsSetInContextStore', () => {
|
||||
@@ -96,6 +108,18 @@ describe('EnvironmentTabBar', () => {
|
||||
|
||||
expect(contextStore.currentEnvironment).toEqual(mockEnvironments[0]);
|
||||
});
|
||||
|
||||
it('ThenCreateEnvironmentButtonIsEnabled', async () => {
|
||||
jest.mocked(envApi.listEnvironments).mockResolvedValue(mockEnvironments);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="create-env-btn"]').attributes('disabled')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenEnvironmentChipIsClicked', () => {
|
||||
@@ -108,7 +132,6 @@ describe('EnvironmentTabBar', () => {
|
||||
contextStore.setProject(mockProject);
|
||||
await flushPromises();
|
||||
|
||||
// Simulate chip-group selecting index 1 (Production)
|
||||
await wrapper.findComponent({ name: 'VChipGroup' }).vm.$emit('update:modelValue', 1);
|
||||
|
||||
expect(contextStore.currentEnvironment).toEqual(mockEnvironments[1]);
|
||||
@@ -147,4 +170,149 @@ describe('EnvironmentTabBar', () => {
|
||||
expect(wrapper.find('[data-testid="env-empty-message"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateEnvironmentButtonIsClicked', () => {
|
||||
it('ThenCreateDialogIsShown', async () => {
|
||||
jest.mocked(envApi.listEnvironments).mockResolvedValue([]);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-env-btn"]').trigger('click');
|
||||
|
||||
expect(wrapper.find('[data-testid="create-env-dialog"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenNameInputIsPresentInDialog', async () => {
|
||||
jest.mocked(envApi.listEnvironments).mockResolvedValue([]);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-env-btn"]').trigger('click');
|
||||
|
||||
expect(wrapper.find('[data-testid="env-name-input"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateEnvironmentIsConfirmed', () => {
|
||||
const newEnv: EnvironmentResponse = {
|
||||
id: 200, name: 'Staging', apiKey: 'env-key-staging', projectId: 10, createdAt: '2026-04-14T00:00:00Z',
|
||||
};
|
||||
|
||||
it('ThenCreateEnvironmentApiIsCalled', async () => {
|
||||
jest.mocked(envApi.listEnvironments).mockResolvedValue([]);
|
||||
jest.mocked(envApi.createEnvironment).mockResolvedValue(newEnv);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-env-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newEnvName = 'Staging';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect(envApi.createEnvironment).toHaveBeenCalledWith({ name: 'Staging', projectId: mockProject.id });
|
||||
});
|
||||
|
||||
it('ThenNewEnvironmentIsAutoSelectedInContextStore', async () => {
|
||||
jest.mocked(envApi.listEnvironments).mockResolvedValue([]);
|
||||
jest.mocked(envApi.createEnvironment).mockResolvedValue(newEnv);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-env-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newEnvName = 'Staging';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect(contextStore.currentEnvironment).toEqual(newEnv);
|
||||
});
|
||||
|
||||
it('ThenDialogIsClosedAfterSuccessfulCreate', async () => {
|
||||
jest.mocked(envApi.listEnvironments).mockResolvedValue([]);
|
||||
jest.mocked(envApi.createEnvironment).mockResolvedValue(newEnv);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-env-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newEnvName = 'Staging';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect((wrapper.vm as any).showDialog).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateEnvironmentFails', () => {
|
||||
it('ThenErrorMessageIsDisplayed', async () => {
|
||||
jest.mocked(envApi.listEnvironments).mockResolvedValue([]);
|
||||
jest.mocked(envApi.createEnvironment).mockRejectedValue(new Error('Server error'));
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-env-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newEnvName = 'Bad Env';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect((wrapper.vm as any).createError).toBeTruthy();
|
||||
});
|
||||
|
||||
it('ThenDialogRemainsOpen', async () => {
|
||||
jest.mocked(envApi.listEnvironments).mockResolvedValue([]);
|
||||
jest.mocked(envApi.createEnvironment).mockRejectedValue(new Error('Server error'));
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-env-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newEnvName = 'Bad Env';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect((wrapper.vm as any).showDialog).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCancelIsClicked', () => {
|
||||
it('ThenDialogIsClosed', async () => {
|
||||
jest.mocked(envApi.listEnvironments).mockResolvedValue([]);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-env-btn"]').trigger('click');
|
||||
await wrapper.find('[data-testid="create-env-cancel-btn"]').trigger('click');
|
||||
|
||||
expect((wrapper.vm as any).showDialog).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,8 @@ jest.mock('@/api/client', () => ({
|
||||
|
||||
import * as orgsApi from '@/api/organizations';
|
||||
|
||||
const dialogStub = { template: '<div><slot /></div>' };
|
||||
|
||||
const mockOrgs: OrganizationResponse[] = [
|
||||
{ id: 1, name: 'Acme Corp', createdAt: '2026-01-01T00:00:00Z' },
|
||||
{ id: 2, name: 'Globex', createdAt: '2026-01-01T00:00:00Z' },
|
||||
@@ -26,7 +28,10 @@ function mountComponent() {
|
||||
routes: [{ path: '/', component: { template: '<div />' } }],
|
||||
});
|
||||
return mount(OrgSelector, {
|
||||
global: { plugins: [router] },
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: { 'v-dialog': dialogStub },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -55,6 +60,15 @@ describe('OrgSelector', () => {
|
||||
const select = wrapper.find('[data-testid="org-select"]');
|
||||
expect(select.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenCreateOrganizationButtonIsAlwaysVisible', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue(mockOrgs);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="create-org-btn"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenOrganizationIsSelected', () => {
|
||||
@@ -65,7 +79,6 @@ describe('OrgSelector', () => {
|
||||
await flushPromises();
|
||||
|
||||
const contextStore = useContextStore();
|
||||
// Simulate the v-select emitting an update
|
||||
await wrapper.findComponent({ name: 'VSelect' }).vm.$emit('update:modelValue', mockOrgs[0]);
|
||||
|
||||
expect(contextStore.currentOrganization).toEqual(mockOrgs[0]);
|
||||
@@ -73,14 +86,13 @@ describe('OrgSelector', () => {
|
||||
});
|
||||
|
||||
describe('WhenApiReturnsNoOrganizations', () => {
|
||||
it('ThenEmptyMessageIsDisplayed', async () => {
|
||||
it('ThenCreateButtonIsStillVisible', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue([]);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
const emptyMsg = wrapper.find('[data-testid="org-empty-message"]');
|
||||
expect(emptyMsg.exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="create-org-btn"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,8 +103,129 @@ describe('OrgSelector', () => {
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
// No crash — component stays rendered
|
||||
expect(wrapper.find('[data-testid="org-selector"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateOrganizationButtonIsClicked', () => {
|
||||
it('ThenCreateDialogIsShown', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue([]);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-org-btn"]').trigger('click');
|
||||
|
||||
expect(wrapper.find('[data-testid="create-org-dialog"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenNameInputIsPresentInDialog', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue([]);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-org-btn"]').trigger('click');
|
||||
|
||||
expect(wrapper.find('[data-testid="org-name-input"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateOrganizationIsConfirmed', () => {
|
||||
const newOrg: OrganizationResponse = { id: 3, name: 'New Org', createdAt: '2026-04-13T00:00:00Z' };
|
||||
|
||||
it('ThenCreateOrganizationApiIsCalled', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue([]);
|
||||
jest.mocked(orgsApi.createOrganization).mockResolvedValue(newOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-org-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newOrgName = 'New Org';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect(orgsApi.createOrganization).toHaveBeenCalledWith({ name: 'New Org' });
|
||||
});
|
||||
|
||||
it('ThenNewOrgIsAutoSelectedInContextStore', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue([]);
|
||||
jest.mocked(orgsApi.createOrganization).mockResolvedValue(newOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
const contextStore = useContextStore();
|
||||
|
||||
await wrapper.find('[data-testid="create-org-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newOrgName = 'New Org';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect(contextStore.currentOrganization).toEqual(newOrg);
|
||||
});
|
||||
|
||||
it('ThenDialogIsClosedAfterSuccessfulCreate', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue([]);
|
||||
jest.mocked(orgsApi.createOrganization).mockResolvedValue(newOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-org-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newOrgName = 'New Org';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect((wrapper.vm as any).showDialog).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateOrganizationFails', () => {
|
||||
it('ThenErrorMessageIsDisplayed', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue([]);
|
||||
jest.mocked(orgsApi.createOrganization).mockRejectedValue(new Error('Server error'));
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-org-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newOrgName = 'Bad Org';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect((wrapper.vm as any).createError).toBeTruthy();
|
||||
});
|
||||
|
||||
it('ThenDialogRemainsOpen', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue([]);
|
||||
jest.mocked(orgsApi.createOrganization).mockRejectedValue(new Error('Server error'));
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-org-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newOrgName = 'Bad Org';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect((wrapper.vm as any).showDialog).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCancelIsClicked', () => {
|
||||
it('ThenDialogIsClosed', async () => {
|
||||
jest.mocked(orgsApi.listOrganizations).mockResolvedValue([]);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-org-btn"]').trigger('click');
|
||||
await wrapper.find('[data-testid="create-org-cancel-btn"]').trigger('click');
|
||||
|
||||
const vm = wrapper.vm as any;
|
||||
expect(vm.showDialog).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,8 @@ jest.mock('@/api/client', () => ({
|
||||
|
||||
import * as projectsApi from '@/api/projects';
|
||||
|
||||
const dialogStub = { template: '<div><slot /></div>' };
|
||||
|
||||
const mockOrg: OrganizationResponse = { id: 1, name: 'Acme', createdAt: '2026-01-01T00:00:00Z' };
|
||||
const mockProjects: ProjectResponse[] = [
|
||||
{ id: 10, name: 'Website', organizationId: 1, hideDisabledFlags: false, createdAt: '2026-01-01T00:00:00Z' },
|
||||
@@ -27,7 +29,10 @@ function mountComponent() {
|
||||
routes: [{ path: '/', component: { template: '<div />' } }],
|
||||
});
|
||||
return mount(ProjectSelector, {
|
||||
global: { plugins: [router] },
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: { 'v-dialog': dialogStub },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,6 +50,15 @@ describe('ProjectSelector', () => {
|
||||
expect(projectsApi.listProjects).not.toHaveBeenCalled();
|
||||
expect(wrapper.find('[data-testid="project-selector"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenCreateProjectButtonIsDisabled', async () => {
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
const btn = wrapper.find('[data-testid="create-project-btn"]');
|
||||
expect(btn.exists()).toBe(true);
|
||||
expect(btn.attributes('disabled')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenOrganizationIsSetInContextStore', () => {
|
||||
@@ -59,6 +73,19 @@ describe('ProjectSelector', () => {
|
||||
|
||||
expect(projectsApi.listProjects).toHaveBeenCalledWith(mockOrg.id);
|
||||
});
|
||||
|
||||
it('ThenCreateProjectButtonIsEnabled', async () => {
|
||||
jest.mocked(projectsApi.listProjects).mockResolvedValue(mockProjects);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
const btn = wrapper.find('[data-testid="create-project-btn"]');
|
||||
expect(btn.attributes('disabled')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenProjectIsSelected', () => {
|
||||
@@ -95,4 +122,149 @@ describe('ProjectSelector', () => {
|
||||
expect(projectsApi.listProjects).toHaveBeenLastCalledWith(newOrg.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateProjectButtonIsClicked', () => {
|
||||
it('ThenCreateDialogIsShown', async () => {
|
||||
jest.mocked(projectsApi.listProjects).mockResolvedValue([]);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-project-btn"]').trigger('click');
|
||||
|
||||
expect(wrapper.find('[data-testid="create-project-dialog"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenNameInputIsPresentInDialog', async () => {
|
||||
jest.mocked(projectsApi.listProjects).mockResolvedValue([]);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-project-btn"]').trigger('click');
|
||||
|
||||
expect(wrapper.find('[data-testid="project-name-input"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateProjectIsConfirmed', () => {
|
||||
const newProject: ProjectResponse = {
|
||||
id: 99, name: 'New Project', organizationId: 1, hideDisabledFlags: false, createdAt: '2026-04-13T00:00:00Z',
|
||||
};
|
||||
|
||||
it('ThenCreateProjectApiIsCalled', async () => {
|
||||
jest.mocked(projectsApi.listProjects).mockResolvedValue([]);
|
||||
jest.mocked(projectsApi.createProject).mockResolvedValue(newProject);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-project-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newProjectName = 'New Project';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect(projectsApi.createProject).toHaveBeenCalledWith({ name: 'New Project', organizationId: mockOrg.id });
|
||||
});
|
||||
|
||||
it('ThenNewProjectIsAutoSelectedInContextStore', async () => {
|
||||
jest.mocked(projectsApi.listProjects).mockResolvedValue([]);
|
||||
jest.mocked(projectsApi.createProject).mockResolvedValue(newProject);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-project-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newProjectName = 'New Project';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect(contextStore.currentProject).toEqual(newProject);
|
||||
});
|
||||
|
||||
it('ThenDialogIsClosedAfterSuccessfulCreate', async () => {
|
||||
jest.mocked(projectsApi.listProjects).mockResolvedValue([]);
|
||||
jest.mocked(projectsApi.createProject).mockResolvedValue(newProject);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-project-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newProjectName = 'New Project';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect((wrapper.vm as any).showDialog).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateProjectFails', () => {
|
||||
it('ThenErrorMessageIsDisplayed', async () => {
|
||||
jest.mocked(projectsApi.listProjects).mockResolvedValue([]);
|
||||
jest.mocked(projectsApi.createProject).mockRejectedValue(new Error('Server error'));
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-project-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newProjectName = 'Bad Project';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect((wrapper.vm as any).createError).toBeTruthy();
|
||||
});
|
||||
|
||||
it('ThenDialogRemainsOpen', async () => {
|
||||
jest.mocked(projectsApi.listProjects).mockResolvedValue([]);
|
||||
jest.mocked(projectsApi.createProject).mockRejectedValue(new Error('Server error'));
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-project-btn"]').trigger('click');
|
||||
(wrapper.vm as any).newProjectName = 'Bad Project';
|
||||
await (wrapper.vm as any).onCreateConfirm();
|
||||
await flushPromises();
|
||||
|
||||
expect((wrapper.vm as any).showDialog).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCancelIsClicked', () => {
|
||||
it('ThenDialogIsClosed', async () => {
|
||||
jest.mocked(projectsApi.listProjects).mockResolvedValue([]);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountComponent();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="create-project-btn"]').trigger('click');
|
||||
await wrapper.find('[data-testid="create-project-cancel-btn"]').trigger('click');
|
||||
|
||||
expect((wrapper.vm as any).showDialog).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
85
src/admin/tests/unit/views/ProfileView.spec.ts
Normal file
85
src/admin/tests/unit/views/ProfileView.spec.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { mount, flushPromises } from '@vue/test-utils';
|
||||
import { setActivePinia, createPinia } from 'pinia';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import ProfileView from '@/views/ProfileView.vue';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { useContextStore } from '@/stores/context';
|
||||
|
||||
jest.mock('@/api/auth');
|
||||
jest.mock('@/api/client', () => ({
|
||||
setAuthTokens: jest.fn(),
|
||||
clearAuthTokens: jest.fn(),
|
||||
getAccessToken: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as authApi from '@/api/auth';
|
||||
|
||||
function mountView() {
|
||||
const routes = [
|
||||
{ path: '/', component: { template: '<div />' } },
|
||||
{ path: '/login', name: 'Login', component: { template: '<div />' } },
|
||||
];
|
||||
const router = createRouter({ history: createMemoryHistory(), routes });
|
||||
return { wrapper: mount(ProfileView, { global: { plugins: [router] } }), router };
|
||||
}
|
||||
|
||||
describe('ProfileView', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('WhenAuthenticated', () => {
|
||||
it('ThenProfileCardIsRendered', () => {
|
||||
const { wrapper } = mountView();
|
||||
expect(wrapper.find('[data-testid="profile-card"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenLogoutButtonIsPresent', () => {
|
||||
const { wrapper } = mountView();
|
||||
expect(wrapper.find('[data-testid="logout-btn"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenOrgNameIsDisplayedWhenSelected', () => {
|
||||
const { wrapper } = mountView();
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization({ id: 1, name: 'Acme Corp', createdAt: '' });
|
||||
|
||||
expect(wrapper.find('[data-testid="profile-org-name"]').exists()).toBe(false); // org name shown in v-if block
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenLogoutButtonIsClicked', () => {
|
||||
it('ThenAuthStoreLogoutIsCalled', async () => {
|
||||
jest.mocked(authApi.logout).mockResolvedValue(undefined);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
// Simulate logged-in state by setting tokens manually
|
||||
authStore.accessToken = 'fake-token' as any;
|
||||
authStore.refreshToken = 'fake-refresh' as any;
|
||||
|
||||
const { wrapper } = mountView();
|
||||
|
||||
await wrapper.find('[data-testid="logout-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(authApi.logout).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ThenUserIsRedirectedToLoginAfterLogout', async () => {
|
||||
jest.mocked(authApi.logout).mockResolvedValue(undefined);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
authStore.accessToken = 'fake-token' as any;
|
||||
authStore.refreshToken = 'fake-refresh' as any;
|
||||
|
||||
const { wrapper, router } = mountView();
|
||||
|
||||
await wrapper.find('[data-testid="logout-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(router.currentRoute.value.name).toBe('Login');
|
||||
});
|
||||
});
|
||||
});
|
||||
480
src/admin/tests/unit/views/SettingsView.spec.ts
Normal file
480
src/admin/tests/unit/views/SettingsView.spec.ts
Normal file
@@ -0,0 +1,480 @@
|
||||
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
||||
import { mount, flushPromises, type VueWrapper } from '@vue/test-utils';
|
||||
import { setActivePinia, createPinia } from 'pinia';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import SettingsView from '@/views/SettingsView.vue';
|
||||
import { useContextStore } from '@/stores/context';
|
||||
import type {
|
||||
ProjectResponse, OrganizationResponse, EnvironmentResponse,
|
||||
UserPermissionResponse, ApiKeyResponse, CreateApiKeyResponse,
|
||||
} from '@/types/api';
|
||||
|
||||
jest.mock('@/api/organizations');
|
||||
jest.mock('@/api/projects');
|
||||
jest.mock('@/api/environments');
|
||||
jest.mock('@/api/apiKeys');
|
||||
jest.mock('@/api/webhooks');
|
||||
jest.mock('@/api/client', () => ({
|
||||
setAuthTokens: jest.fn(),
|
||||
clearAuthTokens: jest.fn(),
|
||||
getAccessToken: jest.fn(),
|
||||
}));
|
||||
|
||||
import * as orgsApi from '@/api/organizations';
|
||||
import * as projectsApi from '@/api/projects';
|
||||
import * as environmentsApi from '@/api/environments';
|
||||
import * as apiKeysApi from '@/api/apiKeys';
|
||||
import * as webhooksApi from '@/api/webhooks';
|
||||
|
||||
const mockOrg: OrganizationResponse = { id: 1, name: 'Acme', createdAt: '2026-01-01T00:00:00Z' };
|
||||
const mockProject: ProjectResponse = { id: 10, name: 'Website', organizationId: 1, hideDisabledFlags: false, createdAt: '2026-01-01T00:00:00Z' };
|
||||
const mockEnvs: EnvironmentResponse[] = [
|
||||
{ id: 100, name: 'Development', apiKey: 'env-dev', projectId: 10, createdAt: '2026-01-01T00:00:00Z' },
|
||||
{ id: 200, name: 'Production', apiKey: 'env-prod', projectId: 10, createdAt: '2026-01-02T00:00:00Z' },
|
||||
];
|
||||
const mockApiKeys: ApiKeyResponse[] = [
|
||||
{ id: 1, name: 'CI Key', prefix: 'org-ci', isActive: true, expiresAt: null, createdAt: '2026-01-01T00:00:00Z' },
|
||||
];
|
||||
const mockPermissions: UserPermissionResponse[] = [
|
||||
{ userId: 5, projectId: 10, isAdmin: false, permissions: ['ViewProject', 'EditFeature'] },
|
||||
];
|
||||
|
||||
const dialogStub = { template: '<div><slot /></div>' };
|
||||
|
||||
function mountView() {
|
||||
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/', component: { template: '<div />' } }] });
|
||||
return mount(SettingsView, {
|
||||
global: { plugins: [router], stubs: { 'v-dialog': dialogStub } },
|
||||
});
|
||||
}
|
||||
|
||||
describe('SettingsView', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
jest.clearAllMocks();
|
||||
|
||||
jest.mocked(orgsApi.listOrganizationMembers).mockResolvedValue([]);
|
||||
jest.mocked(projectsApi.listProjectUserPermissions).mockResolvedValue([]);
|
||||
jest.mocked(environmentsApi.listEnvironments).mockResolvedValue([]);
|
||||
jest.mocked(apiKeysApi.listApiKeys).mockResolvedValue([]);
|
||||
jest.mocked(webhooksApi.listOrgWebhooks).mockResolvedValue([]);
|
||||
jest.mocked(webhooksApi.listEnvWebhooks).mockResolvedValue([]);
|
||||
});
|
||||
|
||||
describe('WhenNoOrganizationSelected', () => {
|
||||
it('ThenSelectOrgMessageIsShown', () => {
|
||||
const wrapper = mountView();
|
||||
expect(wrapper.text()).toContain('Select an organization');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenOrganizationIsSelected', () => {
|
||||
it('ThenTabsAreRendered', async () => {
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="tab-organization"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="tab-api-keys"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Organization tab ────────────────────────────────────────────────────────
|
||||
|
||||
describe('WhenSaveOrgNameIsClicked', () => {
|
||||
it('ThenUpdateOrganizationIsCalledWithNewName', async () => {
|
||||
jest.mocked(orgsApi.updateOrganization).mockResolvedValue({ ...mockOrg, name: 'New Name' });
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="org-name-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'New Name');
|
||||
await wrapper.find('[data-testid="save-org-name-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(orgsApi.updateOrganization).toHaveBeenCalledWith(mockOrg.id, { name: 'New Name' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenInviteMemberIsClicked', () => {
|
||||
it('ThenInviteOrganizationMemberIsCalledWithUserId', async () => {
|
||||
jest.mocked(orgsApi.inviteOrganizationMember).mockResolvedValue(undefined);
|
||||
jest.mocked(orgsApi.listOrganizationMembers).mockResolvedValue([{ userId: 42, role: 'User' }]);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="invite-user-id-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', '42');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="invite-member-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(orgsApi.inviteOrganizationMember).toHaveBeenCalledWith(mockOrg.id, { userId: 42, role: 'User' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenRemoveMemberIsClicked', () => {
|
||||
it('ThenRemoveOrganizationMemberIsCalledAndMemberIsRemovedFromList', async () => {
|
||||
jest.mocked(orgsApi.listOrganizationMembers).mockResolvedValue([{ userId: 5, role: 'User' }]);
|
||||
jest.mocked(orgsApi.removeOrganizationMember).mockResolvedValue(undefined);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find('[data-testid="member-row-5"]').exists()).toBe(true);
|
||||
|
||||
await wrapper.find('[data-testid="remove-member-5"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(orgsApi.removeOrganizationMember).toHaveBeenCalledWith(mockOrg.id, 5);
|
||||
expect(wrapper.find('[data-testid="member-row-5"]').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Project tab ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('WhenSaveProjectIsClicked', () => {
|
||||
it('ThenUpdateProjectIsCalledWithFormValues', async () => {
|
||||
jest.mocked(projectsApi.updateProject).mockResolvedValue(mockProject);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-project"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="save-project-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(projectsApi.updateProject).toHaveBeenCalledWith(
|
||||
mockProject.id,
|
||||
expect.objectContaining({ name: mockProject.name }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenPermissionsAreLoaded', () => {
|
||||
it('ThenPermissionRowsAreDisplayed', async () => {
|
||||
jest.mocked(projectsApi.listProjectUserPermissions).mockResolvedValue(mockPermissions);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-project"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.find('[data-testid="permission-row-5"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenAdminToggleIsChanged', () => {
|
||||
it('ThenUpdateProjectUserPermissionsIsCalledWithIsAdmin', async () => {
|
||||
jest.mocked(projectsApi.listProjectUserPermissions).mockResolvedValue(mockPermissions);
|
||||
jest.mocked(projectsApi.updateProjectUserPermissions).mockResolvedValue({
|
||||
...mockPermissions[0], isAdmin: true,
|
||||
});
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-project"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await (wrapper.findComponent(`[data-testid="perm-admin-toggle-5"]`) as VueWrapper<any>).vm.$emit('update:modelValue', true);
|
||||
await flushPromises();
|
||||
|
||||
expect(projectsApi.updateProjectUserPermissions).toHaveBeenCalledWith(
|
||||
mockProject.id, 5,
|
||||
expect.objectContaining({ isAdmin: true }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenAddUserPermissionsIsSubmitted', () => {
|
||||
it('ThenSetProjectUserPermissionsIsCalledWithNewUserId', async () => {
|
||||
const newPerm: UserPermissionResponse = { userId: 99, projectId: 10, isAdmin: false, permissions: ['ViewProject'] };
|
||||
jest.mocked(projectsApi.setProjectUserPermissions).mockResolvedValue(newPerm);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-project"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="add-permission-btn"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="new-perm-user-id-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', '99');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="save-new-permission-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(projectsApi.setProjectUserPermissions).toHaveBeenCalledWith(
|
||||
mockProject.id,
|
||||
expect.objectContaining({ userId: 99 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenRemovePermissionIsClicked', () => {
|
||||
it('ThenRemoveProjectUserPermissionsIsCalledAndRowIsRemoved', async () => {
|
||||
jest.mocked(projectsApi.listProjectUserPermissions).mockResolvedValue(mockPermissions);
|
||||
jest.mocked(projectsApi.removeProjectUserPermissions).mockResolvedValue(undefined);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-project"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="remove-permission-5"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(projectsApi.removeProjectUserPermissions).toHaveBeenCalledWith(mockProject.id, 5);
|
||||
expect(wrapper.find('[data-testid="permission-row-5"]').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Environments tab ────────────────────────────────────────────────────────
|
||||
|
||||
describe('WhenCreateEnvironmentIsClicked', () => {
|
||||
it('ThenCreateEnvironmentIsCalledAndEnvIsAddedToList', async () => {
|
||||
jest.mocked(environmentsApi.listEnvironments).mockResolvedValue([]);
|
||||
jest.mocked(environmentsApi.createEnvironment).mockResolvedValue(mockEnvs[0]);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-environments"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="new-env-name-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'Staging');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="create-env-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(environmentsApi.createEnvironment).toHaveBeenCalledWith({ projectId: mockProject.id, name: 'Staging' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCloneEnvironmentIsConfirmed', () => {
|
||||
it('ThenCloneEnvironmentIsCalledWithNewName', async () => {
|
||||
jest.mocked(environmentsApi.listEnvironments).mockResolvedValue([...mockEnvs]);
|
||||
jest.mocked(environmentsApi.cloneEnvironment).mockResolvedValue({
|
||||
...mockEnvs[0], id: 300, name: 'Staging', apiKey: 'env-stg',
|
||||
});
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-environments"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="clone-env-100"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="clone-env-name-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'Staging');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="confirm-clone-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(environmentsApi.cloneEnvironment).toHaveBeenCalledWith(mockEnvs[0].apiKey, { name: 'Staging' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenDeleteEnvironmentIsConfirmed', () => {
|
||||
it('ThenDeleteEnvironmentIsCalledAndEnvIsRemovedFromList', async () => {
|
||||
jest.mocked(environmentsApi.listEnvironments).mockResolvedValue([...mockEnvs]);
|
||||
jest.mocked(environmentsApi.deleteEnvironment).mockResolvedValue(undefined);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-environments"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="delete-env-100"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="delete-env-confirm-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'Development');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="confirm-delete-env-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(environmentsApi.deleteEnvironment).toHaveBeenCalledWith(mockEnvs[0].apiKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenRenameEnvironmentIsConfirmed', () => {
|
||||
it('ThenUpdateEnvironmentIsCalledWithNewName', async () => {
|
||||
jest.mocked(environmentsApi.listEnvironments).mockResolvedValue([...mockEnvs]);
|
||||
jest.mocked(environmentsApi.updateEnvironment).mockResolvedValue({ ...mockEnvs[0], name: 'Dev Renamed' });
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
contextStore.setProject(mockProject);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-environments"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="rename-env-100"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="rename-env-name-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'Dev Renamed');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="confirm-rename-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(environmentsApi.updateEnvironment).toHaveBeenCalledWith(mockEnvs[0].apiKey, { name: 'Dev Renamed' });
|
||||
});
|
||||
});
|
||||
|
||||
// ── API Keys tab ────────────────────────────────────────────────────────────
|
||||
|
||||
describe('WhenApiKeysTabIsOpened', () => {
|
||||
it('ThenApiKeysAreLoaded', async () => {
|
||||
jest.mocked(apiKeysApi.listApiKeys).mockResolvedValue(mockApiKeys);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-api-keys"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.find('[data-testid="api-key-row-1"]').exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('CI Key');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenCreateApiKeyIsSubmitted', () => {
|
||||
it('ThenCreateApiKeyIsCalledAndRawKeyDialogIsShown', async () => {
|
||||
const created: CreateApiKeyResponse = { id: 99, name: 'My Key', rawKey: 'raw-secret-key', prefix: 'org-my', expiresAt: null };
|
||||
jest.mocked(apiKeysApi.createApiKey).mockResolvedValue(created);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-api-keys"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="create-api-key-btn"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="api-key-name-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'My Key');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="save-api-key-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(apiKeysApi.createApiKey).toHaveBeenCalledWith(mockOrg.id, expect.objectContaining({ name: 'My Key' }));
|
||||
expect(wrapper.find('[data-testid="raw-key-dialog"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="raw-key-field"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('ThenRawKeyIsDisplayedInTheRevealDialog', async () => {
|
||||
const created: CreateApiKeyResponse = { id: 99, name: 'My Key', rawKey: 'raw-secret-key', prefix: 'org-my', expiresAt: null };
|
||||
jest.mocked(apiKeysApi.createApiKey).mockResolvedValue(created);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-api-keys"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="create-api-key-btn"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await (wrapper.findComponent('[data-testid="api-key-name-input"]') as VueWrapper<any>).vm.$emit('update:modelValue', 'My Key');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="save-api-key-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
const rawKeyField = wrapper.findComponent('[data-testid="raw-key-field"]') as VueWrapper<any>;
|
||||
expect(rawKeyField.props('modelValue')).toBe('raw-secret-key');
|
||||
});
|
||||
});
|
||||
|
||||
describe('WhenDeleteApiKeyIsClicked', () => {
|
||||
it('ThenDeleteApiKeyIsCalledAndKeyIsRemovedFromList', async () => {
|
||||
jest.mocked(apiKeysApi.listApiKeys).mockResolvedValue([...mockApiKeys]);
|
||||
jest.mocked(apiKeysApi.deleteApiKey).mockResolvedValue(undefined);
|
||||
|
||||
const contextStore = useContextStore();
|
||||
contextStore.setOrganization(mockOrg);
|
||||
|
||||
const wrapper = mountView();
|
||||
await flushPromises();
|
||||
|
||||
await wrapper.find('[data-testid="tab-api-keys"]').trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
await wrapper.find('[data-testid="delete-api-key-1"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
expect(apiKeysApi.deleteApiKey).toHaveBeenCalledWith(mockOrg.id, 1);
|
||||
expect(wrapper.find('[data-testid="api-key-row-1"]').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user