Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src/components: add ability to create organization and subsidiary in FormFieldSelectTable #804

Merged
merged 20 commits into from
Dec 25, 2024
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
7 changes: 7 additions & 0 deletions src/components/__tests__/FormFieldCompanyAddress.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ describe('<FormFieldCompanyAddress>', () => {
// set organization ID in store
store.setOrganizationId(organizationId);
cy.wrap(store.getOrganizationId).should('eq', organizationId);
/**
* Manually load subsidiaries.
* In the live application, this is handled by `onMounted` hook,
* or calling `loadSubsidiariesToStore` method in another
* component.
*/
store.loadSubsidiariesToStore(null);
// wait for subsidiaries API
cy.waitForSubsidiariesApi(
subsidiariesResponse,
Expand Down
94 changes: 94 additions & 0 deletions src/components/__tests__/FormFieldSelectTable.cy.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { ref } from 'vue';
import FormFieldSelectTable from 'components/form/FormFieldSelectTable.vue';
import { rideToWorkByBikeConfig } from 'src/boot/global_vars';
import { i18n } from '../../boot/i18n';
import { useApiGetOrganizations } from 'src/composables/useApiGetOrganizations';
import { createPinia, setActivePinia } from 'pinia';
import { vModelAdapter } from 'app/test/cypress/utils';
import {
OrganizationLevel,
OrganizationType,
} from 'src/components/types/Organization';
import { interceptOrganizationsApi } from '../../../test/cypress/support/commonTests';
import { useRegisterChallengeStore } from 'src/stores/registerChallenge';

// variables
const { contactEmail } = rideToWorkByBikeConfig;
const model = ref(null);

describe('<FormFieldSelectTable>', () => {
let options;
let organizationId;
let subsidiaryId;

before(() => {
Expand All @@ -32,6 +38,12 @@ describe('<FormFieldSelectTable>', () => {
},
);
});
// set common organizationId from fixture
cy.fixture('formFieldCompanyCreate').then(
(formFieldCompanyCreateResponse) => {
organizationId = formFieldCompanyCreateResponse.id;
},
);
// set common subsidiaryId from fixture
cy.fixture('formOrganizationOptions').then((formOrganizationOptions) => {
subsidiaryId = formOrganizationOptions[0].subsidiaries[0].id;
Expand Down Expand Up @@ -95,8 +107,20 @@ describe('<FormFieldSelectTable>', () => {
context('organization company', () => {
beforeEach(() => {
cy.interceptCitiesGetApi(rideToWorkByBikeConfig, i18n);
// intercept both POST and GET requests for organizations
interceptOrganizationsApi(
rideToWorkByBikeConfig,
i18n,
OrganizationType.company,
);
cy.interceptSubsidiaryPostApi(
rideToWorkByBikeConfig,
i18n,
organizationId,
);
cy.mount(FormFieldSelectTable, {
props: {
...vModelAdapter(model),
options: options,
organizationLevel: OrganizationLevel.organization,
organizationType: OrganizationType.company,
Expand Down Expand Up @@ -219,6 +243,76 @@ describe('<FormFieldSelectTable>', () => {
cy.dataCy('dialog-button-submit').click();
cy.dataCy('dialog-add-option').should('not.exist');
});

it('allows to add a new organization', () => {
cy.fixture('apiPostSubsidiaryRequest').then(
(apiPostSubsidiaryRequest) => {
cy.fixture('formFieldCompanyCreateRequest').then(
(formFieldCompanyCreateRequest) => {
// open add company dialog
cy.dataCy('button-add-option').click();
// verify that dialog is visible
cy.dataCy('dialog-add-option').should('be.visible');
cy.dataCy('dialog-add-option')
.find('h3')
.should('be.visible')
.and('contain', i18n.global.t('form.company.titleAddCompany'));
// fill in the form
cy.fillOrganizationSubsidiaryForm(
formFieldCompanyCreateRequest,
apiPostSubsidiaryRequest,
);
// submit the form
cy.dataCy('dialog-button-submit').click();
// wait for API call
cy.waitForOrganizationPostApi();
// verify that dialog is closed
cy.dataCy('dialog-add-option').should('not.exist');
// test emitted events
cy.fixture('formFieldCompanyCreate').then(
(formFieldCompanyCreateResponse) => {
// test that create:option event was emitted
cy.wrap(Cypress.vueWrapper.emitted('create:option')).should(
'have.length',
1,
);
// test that event payload is correct
cy.wrap(
Cypress.vueWrapper.emitted('create:option')[0][0],
).should('deep.equal', formFieldCompanyCreateResponse);
// test that model value was updated
cy.wrap(model)
.its('value')
.should('eq', formFieldCompanyCreateResponse.id);
},
);
// open dialog again
cy.dataCy('button-add-option').click();
cy.dataCy('dialog-add-option').should('be.visible');
// verify that dialog form was reset
cy.dataCy('form-add-company-name')
.find('input')
.should('have.value', '');
cy.dataCy('form-add-company-vat-id')
.find('input')
.should('have.value', '');
cy.dataCy('form-add-subsidiary-street')
.find('input')
.should('have.value', '');
cy.dataCy('form-add-subsidiary-house-number')
.find('input')
.should('have.value', '');
cy.dataCy('form-add-subsidiary-city')
.find('input')
.should('have.value', '');
cy.dataCy('form-add-subsidiary-zip')
.find('input')
.should('have.value', '');
},
);
},
);
});
});

context('organization company selected', () => {
Expand Down
133 changes: 131 additions & 2 deletions src/components/__tests__/FormSelectOrganization.cy.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
import { computed } from 'vue';
import { createPinia, setActivePinia } from 'pinia';
import FormSelectOrganization from 'components/form/FormSelectOrganization.vue';
import { i18n } from '../../boot/i18n';
import { OrganizationType } from 'src/components/types/Organization';
import { useRegisterChallengeStore } from 'src/stores/registerChallenge';
import { interceptOrganizationsApi } from '../../../test/cypress/support/commonTests';
import { rideToWorkByBikeConfig } from 'src/boot/global_vars';

// selectors
const selectorFormFieldCompanyAddress = 'form-company-address';
const selectorFormFieldSelectTable = 'form-select-table-company';
const selectorFormSelectOrganization = 'form-select-organization';

describe('<FormSelectOrganization>', () => {
let organizationId;

it('has translation for all strings', () => {
cy.testLanguageStringsInContext([], 'index.component', i18n);
});

before(() => {
setActivePinia(createPinia());
// set common organizationId from fixture
cy.fixture('formFieldCompanyCreate').then(
(formFieldCompanyCreateResponse) => {
organizationId = formFieldCompanyCreateResponse.id;
},
);
});

context('desktop', () => {
beforeEach(() => {
setActivePinia(createPinia());
cy.interceptSubsidiariesGetApi(
rideToWorkByBikeConfig,
i18n,
organizationId,
);
interceptOrganizationsApi(
rideToWorkByBikeConfig,
i18n,
OrganizationType.company,
);
cy.mount(FormSelectOrganization, {
props: {},
});
Expand All @@ -28,7 +52,16 @@ describe('<FormSelectOrganization>', () => {

context('mobile', () => {
beforeEach(() => {
setActivePinia(createPinia());
cy.interceptSubsidiariesGetApi(
rideToWorkByBikeConfig,
i18n,
organizationId,
);
interceptOrganizationsApi(
rideToWorkByBikeConfig,
i18n,
OrganizationType.company,
);
cy.mount(FormSelectOrganization, {
props: {},
});
Expand All @@ -37,6 +70,102 @@ describe('<FormSelectOrganization>', () => {

coreTests();
});

context('create new organization and subsidiary', () => {
beforeEach(() => {
cy.interceptCitiesGetApi(rideToWorkByBikeConfig, i18n);
interceptOrganizationsApi(
rideToWorkByBikeConfig,
i18n,
OrganizationType.company,
);
cy.interceptSubsidiaryPostApi(
rideToWorkByBikeConfig,
i18n,
organizationId,
);
cy.mount(FormSelectOrganization, {
props: {},
});
cy.viewport('iphone-6');
});

it('allows to create a new organization and subsidiary', () => {
cy.fixture('formFieldCompany').then((formFieldCompanyResponse) => {
cy.fixture('apiPostSubsidiaryRequest').then(
(apiPostSubsidiaryRequest) => {
cy.fixture('apiPostSubsidiaryResponse').then(
(apiPostSubsidiaryResponse) => {
cy.fixture('formFieldCompanyCreateRequest').then(
(formFieldCompanyCreateRequest) => {
cy.fixture('formFieldCompanyCreate').then(
(formFieldCompanyCreateResponse) => {
cy.wrap(useRegisterChallengeStore()).then((store) => {
store.setOrganizationType(OrganizationType.company);
const organizationId = computed(
() => store.getOrganizationId,
);
const subsidiaryId = computed(
() => store.getSubsidiaryId,
);
// open dialog form
cy.dataCy('button-add-option').click();
// verify that dialog is visible
cy.dataCy('dialog-add-option').should('be.visible');
cy.dataCy('dialog-add-option')
.find('h3')
.should('be.visible')
.and(
'contain',
i18n.global.t('form.company.titleAddCompany'),
);
// fill in the form
cy.fillOrganizationSubsidiaryForm(
formFieldCompanyCreateRequest,
apiPostSubsidiaryRequest,
);
// submit the form
cy.dataCy('dialog-button-submit').click();
// check that POST organization API was called
cy.waitForOrganizationPostApi();
// check that POST subsidiary API was called
cy.waitForSubsidiaryPostApi();
// check that a new organization was added to the list
cy.dataCy('form-select-table-options')
.find('.q-radio__label')
.should(
'have.length',
formFieldCompanyResponse.count + 1,
);
cy.dataCy('form-select-table-options')
.find('.q-radio__label')
.contains(formFieldCompanyCreateRequest.name)
.should('exist');
// check that organizationId was saved in store
cy.wrap(organizationId)
.its('value')
.should('equal', formFieldCompanyCreateResponse.id);
// check that subsidiaryId was saved in store
cy.wrap(subsidiaryId)
.its('value')
.should('equal', apiPostSubsidiaryResponse.id);
// check that the subsidary (address) input has correct value
cy.dataCy('form-company-address').should(
'contain',
apiPostSubsidiaryRequest.address.street,
);
});
},
);
},
);
},
);
},
);
});
});
});
});

function coreTests() {
Expand Down
2 changes: 1 addition & 1 deletion src/components/form/FormAddCompany.vue
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export default defineComponent({
{{ $t('form.company.textSubsidiaryAddress') }}
</p>
</div>
<!-- TODO: validate the method of accessing address -->
<!-- Subsidiary address fields -->
<form-add-subsidiary
v-model="company.address"
@update:model-value="onUpdate"
Expand Down
36 changes: 7 additions & 29 deletions src/components/form/FormFieldCompanyAddress.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import DialogDefault from 'src/components/global/DialogDefault.vue';
import FormAddSubsidiary from 'src/components/form/FormAddSubsidiary.vue';

// composables
import { useApiGetSubsidiaries } from 'src/composables/useApiGetSubsidiaries';
import { useValidation } from 'src/composables/useValidation';
import { useApiPostSubsidiary } from '../../composables/useApiPostSubsidiary';

Expand Down Expand Up @@ -86,55 +85,34 @@ export default defineComponent({
) as FormCompanyAddressFields,
);

const {
subsidiaries,
isLoading: isLoadingSubsidiaries,
loadSubsidiaries,
} = useApiGetSubsidiaries(logger);
const store = useRegisterChallengeStore();

const isLoadingSubsidiaries = computed(() => store.isLoadingSubsidiaries);
const subsidiaries = computed(() => store.subsidiaries);

const { isLoading: isLoadingCreateSubsidiary, createSubsidiary } =
useApiPostSubsidiary(logger);
const isLoading = computed(
() => isLoadingSubsidiaries.value || isLoadingCreateSubsidiary.value,
);

const store = useRegisterChallengeStore();
/**
* If organization ID is set, load subsidiaries.
* This ensures, that options are loaded on page refresh.
*/
onMounted(async () => {
if (store.getOrganizationId) {
logger?.info('Loading subsidiaries.');
await loadSubsidiaries(store.getOrganizationId);
await store.loadSubsidiariesToStore(logger);
} else {
logger?.debug(
`Organization was not selected <${store.getOrganizationId}>,` +
' subsidiaries was not loaded.',
);
}
});
/**
* Watch for organization ID changes.
* This clears the subsidiary options and state after organization change.
* Then loads subsidiaries for the new organization.
* Should not be triggered on mounted not to clear saved subsidiary ID.
*/
watch(
() => store.getOrganizationId,
(newValue) => {
logger?.debug(
`Register challenge store organization ID updated to <${newValue}>.`,
);
subsidiaryId.value = null;
if (newValue) {
logger?.info('Loading subsidiaries.');
loadSubsidiaries(newValue);
}
},
);

const options = computed(() =>
subsidiaries.value?.map((subsidiary) => ({
store.getSubsidiaries?.map((subsidiary) => ({
label: getAddressString(subsidiary.address),
value: subsidiary.id,
})),
Expand Down
Loading
Loading