Skip to content

Commit

Permalink
test(e2e): add coverage for chart of accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
Betree committed Jan 27, 2025
1 parent b598275 commit e01339f
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 35 deletions.
2 changes: 1 addition & 1 deletion components/dashboard/filters/AccountingCategoryFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,5 @@ function AccountingCategoryFilter({
return categories;
}, [data, intl]);

return <ComboSelectFilter isMulti options={options} loading={loading} {...props} />;
return <ComboSelectFilter data-cy="kind-filter" isMulti options={options} loading={loading} {...props} />;
}
49 changes: 22 additions & 27 deletions components/dashboard/filters/ComboSelectFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function ComboSelectFilter({
creatable,
searchFunc,
valueRenderer,
name,
}: {
value: any;
isMulti?: boolean;
Expand All @@ -128,6 +129,7 @@ function ComboSelectFilter({
creatable?: boolean;
searchFunc?: (term?: string) => void;
valueRenderer?: ({ value }: { value: any }) => React.ReactNode;
name?: string;
}) {
const intl = useIntl();
const [input, setInput] = React.useState('');
Expand All @@ -152,7 +154,7 @@ function ComboSelectFilter({
const selectItems = groupedOptions || getSelectItems({ options, input, selected, creatable, isCreatableOrAsync });

return (
<Command shouldFilter={shouldFilter}>
<Command shouldFilter={shouldFilter} data-cy={`filter-${name}`}>
<CommandInput
autoFocus
loading={loading}
Expand Down Expand Up @@ -219,40 +221,33 @@ export default React.memo(ComboSelectFilter) as typeof ComboSelectFilter;

type ZodDefaultOrOptional<T extends z.ZodTypeAny> = z.ZodDefault<T> | z.ZodOptional<T>;

Check failure on line 222 in components/dashboard/filters/ComboSelectFilter.tsx

View workflow job for this annotation

GitHub Actions / lint

'ZodDefaultOrOptional' is defined but never used

export function buildComboSelectFilter<
Options extends [string, ...string[]],
Schema extends ZodDefaultOrOptional<z.ZodEnum<Options> | z.ZodArray<z.ZodEnum<Options>>>,
>(
schema: Schema,
export function buildComboSelectFilter<T extends z.ZodType>(
schema: T,
labelMsg: MessageDescriptor,
i18nLabels: Record<z.infer<z.ZodEnum<Options>>, MessageDescriptor>,
): FilterConfig<z.infer<Schema>> {
const schemaWithoutDefault = 'removeDefault' in schema ? schema.removeDefault() : schema.unwrap();

const isMulti = 'element' in schemaWithoutDefault;
const schemaEnum = isMulti ? schemaWithoutDefault.element : schemaWithoutDefault;

options: Record<string, MessageDescriptor>,
meta?: Record<string, unknown>,

Check failure on line 228 in components/dashboard/filters/ComboSelectFilter.tsx

View workflow job for this annotation

GitHub Actions / lint

'meta' is defined but never used
): FilterConfig<z.infer<T>> {
return {
schema: isMulti ? schema.or(schemaEnum.transform(val => [val])) : schema,
schema,
filter: {
labelMsg,
Component: ({ intl, ...props }) => (
Component: ({ name, ...props }) => (

Check failure on line 234 in components/dashboard/filters/ComboSelectFilter.tsx

View workflow job for this annotation

GitHub Actions / typescript

Property 'name' does not exist on type 'FilterComponentProps<TypeOf<T>, any>'.
<ComboSelectFilter
isMulti={isMulti}
options={schemaEnum.options
.map(value => {
const label = intl.formatMessage(i18nLabels[value]);
return {
value,
label,
keywords: [label],
};
})
.sort(sortSelectOptions)}
data-cy={`filter-${name}`}
name={name}
{...props}
options={Object.entries(options).map(([value, msg]) => ({
value,
label: props.intl.formatMessage(msg),
}))}
/>
),
valueRenderer: ({ value, intl }) => intl.formatMessage(i18nLabels[value]),
valueRenderer({ value, intl }) {
if (Array.isArray(value)) {
return value.map(v => intl.formatMessage(options[v])).join(', ');
}
return value ? intl.formatMessage(options[value]) : null;
},
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ const columns = [
{
accessorKey: 'code',
header: () => <FormattedMessage id="AccountingCategory.code" defaultMessage="Code" />,
meta: { input: { required: true, maxLength: 255 } },
meta: { input: { required: true, maxLength: 255 }, 'data-cy': 'code-column' },
cell: ({ cell }) => {
return (
<div className="inline-block rounded-xl bg-slate-50 px-2 py-1 font-bold text-slate-800">{cell.getValue()}</div>
<div data-cy="code-cell" className="inline-block rounded-xl bg-slate-50 px-2 py-1 font-bold text-slate-800">
{cell.getValue()}
</div>
);
},
},
Expand All @@ -43,13 +45,15 @@ const columns = [
header: () => (
<FormattedMessage defaultMessage="Name <i>· Friendly name</i>" id="5xKiMX" values={{ i: I18nItalic }} />
),
meta: { input: { required: true, maxLength: 255 } },
meta: { input: { required: true, maxLength: 255 }, 'data-cy': 'name-column' },
cell: ({ cell, row }) => {
return (
<div className="inline-block rounded-xl bg-slate-50 px-2 py-1 font-bold text-slate-800">
<div data-cy="name-cell" className="inline-block rounded-xl bg-slate-50 px-2 py-1 font-bold text-slate-800">
{cell.getValue()}
{row.original.friendlyName && (
<span className="font-normal italic text-slate-700">&nbsp;·&nbsp;{row.original.friendlyName}</span>
<span data-cy="friendly-name" className="font-normal italic text-slate-700">
&nbsp;·&nbsp;{row.original.friendlyName}
</span>
)}
</div>
);
Expand All @@ -69,8 +73,13 @@ const columns = [
{
accessorKey: 'kind',
header: () => <FormattedMessage defaultMessage="Kind" id="Transaction.Kind" />,
meta: { 'data-cy': 'kind-column' },
cell: ({ cell }) => {
return <FormattedMessage {...AccountingCategoryKindI18n[cell.getValue()]} />;
return (
<div data-cy="kind-cell">
<FormattedMessage {...AccountingCategoryKindI18n[cell.getValue()]} />
</div>
);
},
},
{
Expand Down Expand Up @@ -112,10 +121,11 @@ const columns = [
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<TableActionsButton />
<TableActionsButton data-cy="actions-button" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
data-cy="delete-button"
className="cursor-pointer text-red-500"
onClick={() => (table.options.meta as AccountingCategoriesTableMeta).onDelete(row.original)}
>
Expand Down Expand Up @@ -177,6 +187,7 @@ export function AccountingCategoriesTable(props: AccountingCategoriesTableProps)
return (
<React.Fragment>
<DataTable
data-cy="accounting-categories-table"
loading={props.loading}
nbPlaceholders={10}
columns={visibleColumns}
Expand All @@ -195,6 +206,7 @@ export function AccountingCategoriesTable(props: AccountingCategoriesTableProps)
onClickRow={row => setSelectedCategoryId(row.original.id)}
/>
<AccountingCategoryDrawer
data-cy="category-drawer"
isIndependentCollective={isIndependentCollective}
open={!!selectedCategory}
accountingCategory={selectedCategory}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ export function AccountingCategoryForm(props: AccountingCategoryFormProps) {
<StyledSelect
{...props.formik.getFieldProps('kind')}
inputId="kind"
data-cy="kind-select"
options={accountingCategoryKindOptions}
required
width="100%"
Expand All @@ -230,6 +231,7 @@ export function AccountingCategoryForm(props: AccountingCategoryFormProps) {
<StyledSelect
{...props.formik.getFieldProps('hostOnly')}
inputId="hostOnly"
data-cy="host-only-select"
disabled={props.formik.values.kind.value !== AccountingCategoryKind.EXPENSE}
options={hostOnlyOptions}
required
Expand Down
107 changes: 107 additions & 0 deletions test/cypress/integration/31-chart-of-accounts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
describe('Chart of Accounts', () => {
let user;
let host;

before(() => {
cy.signup().then(u => {
user = u;
cy.createHostOrganization(user.email).then(returnedAccount => {
host = returnedAccount;
cy.visit(`/dashboard/${host.slug}/chart-of-accounts`);
});
});
});

it('should support all chart of accounts operations', () => {
// Create category and test form validation
cy.contains('button', 'Create category').click();

// Fill form
cy.get('input[name="code"]').type('TEST001');
cy.get('input[name="name"]').type('Test Category');
cy.get('input[name="friendlyName"]').type('Friendly Test Category');
cy.get('[data-cy="select"]').first().click();
cy.get('[data-cy="select-option"]').contains('Expenses').should('be.visible').click();
cy.get('[data-cy="select"]').eq(1).click();
cy.get('[data-cy="select-option"]').contains('Yes').should('be.visible').click();
cy.contains('button', 'Create category').click();

// Verify category was added
cy.contains('TEST001');
cy.contains('Test Category');
cy.contains('Friendly Test Category');

// Test filtering by kind
cy.get('[data-cy="filter-kind"]').click();
cy.get('[data-cy="combo-select-option"]').contains('Expenses').should('be.visible').click();
cy.get('table').should('contain', 'Expenses');
cy.get('table').should('not.contain', 'Contributions');

// Test filtering by visibility
cy.get('[data-cy="filter-hostOnly"]').click();
cy.get('[data-cy="combo-select-option"]').contains('Yes').should('be.visible').click();
cy.get('table').should('contain', 'Yes');
cy.get('table').should('not.contain', 'No');

// Test search by code
cy.get('[data-cy="filter-searchTerm"]').type('TEST001');
cy.get('table').should('contain', 'TEST001');
cy.get('table').should('not.contain', 'OTHER');
cy.get('[data-cy="filter-searchTerm"]').clear();

// Test search by name
cy.get('[data-cy="filter-searchTerm"]').type('Test Category');
cy.get('table').should('contain', 'Test Category');
cy.get('table').should('not.contain', 'Other Category');
cy.get('[data-cy="filter-searchTerm"]').clear();

// Test search by friendly name
cy.get('[data-cy="filter-searchTerm"]').type('Friendly Test Category');
cy.get('table').should('contain', 'Friendly Test Category');
cy.get('table').should('not.contain', 'Other Friendly Name');
cy.get('[data-cy="filter-searchTerm"]').clear();

// Test sorting by code
cy.get('[data-cy="filter-orderBy"]').click();
cy.get('[data-cy="combo-select-option"]').contains('Code ascending').should('be.visible').click();
cy.get('tbody tr').first().should('contain', 'TEST001');
cy.get('[data-cy="filter-orderBy"]').click();
cy.get('[data-cy="combo-select-option"]').contains('Code descending').should('be.visible').click();
cy.get('tbody tr').first().should('not.contain', 'TEST001');

// Test sorting by name
cy.get('[data-cy="filter-orderBy"]').click();
cy.get('[data-cy="combo-select-option"]').contains('Name ascending').should('be.visible').click();
cy.get('tbody tr').first().should('contain', 'Test Category');
cy.get('[data-cy="filter-orderBy"]').click();
cy.get('[data-cy="combo-select-option"]').contains('Name descending').should('be.visible').click();
cy.get('tbody tr').first().should('not.contain', 'Test Category');

// Test category deletion
cy.get('table').contains('tr', 'TEST001').find('button').click();
cy.contains('Delete').should('be.visible').click();
cy.contains('button', 'Yes').click();
cy.get('table').should('not.contain', 'TEST001');

// Test drawer details
cy.contains('button', 'Create category').click();
cy.get('input[name="code"]').type('VIEW001');
cy.get('input[name="name"]').type('View Category');
cy.get('input[name="friendlyName"]').type('Friendly View Category');
cy.get('[data-cy="select"]').first().click();
cy.get('[data-cy="select-option"]').contains('Expenses').should('be.visible').click();
cy.get('[data-cy="select"]').eq(1).click();
cy.get('[data-cy="select-option"]').contains('Yes').should('be.visible').click();
cy.contains('button', 'Create category').click();

// Open and verify drawer
cy.contains('tr', 'VIEW001').click();
cy.get('[data-cy="category-drawer"]').within(() => {
cy.contains('VIEW001');
cy.contains('View Category');
cy.contains('Friendly View Category');
cy.contains('Expenses');
cy.contains('Yes'); // Host only
});
});
});
40 changes: 40 additions & 0 deletions test/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,46 @@ Cypress.Commands.add('createProject', ({ userEmail = defaultTestUserEmail, colle
});
});

Cypress.Commands.add('createHostOrganization', (userEmail, variables) => {
return signinRequest({ email: userEmail }, null).then(response => {
const token = getTokenFromRedirectUrl(response.body.redirect);
return graphqlQueryV2(token, {
operationName: 'CreateOrganization',
query: gql`
mutation CreateOrganization($organization: OrganizationCreateInput!, $inviteMembers: [InviteMemberInput!]) {
createOrganization(organization: $organization, inviteMembers: $inviteMembers) {
id
legacyId
slug
}
}
`,
variables: {
...variables,
organization: {
slug: randomSlug(),
description: 'Test Host',
name: 'Test Host',
...variables?.organization,
},
},
}).then(({ body }) => {
const host = body.data.createOrganization;
return graphqlQuery(token, {
operationName: 'ActivateCollectiveAsHost',
query: gqlV1`
mutation ActivateCollectiveAsHost($id: Int!) {
activateCollectiveAsHost(id: $id) {
id
}
}
`,
variables: { id: host.legacyId },
}).then(() => host);
});
});
});

Cypress.Commands.add('graphqlQueryV2', (query, { variables = {}, token = null } = {}) => {
return graphqlQueryV2(token, { query, variables }).then(({ body }) => {
return body.data;
Expand Down

0 comments on commit e01339f

Please sign in to comment.