Skip to content

Commit

Permalink
Merge pull request #284 from letehaha/feat/record-form-tests
Browse files Browse the repository at this point in the history
Record creation/modification modal unit tests
  • Loading branch information
letehaha committed Jan 13, 2024
2 parents 1745f86 + 01568be commit b25898c
Show file tree
Hide file tree
Showing 22 changed files with 3,211 additions and 33 deletions.
42 changes: 42 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"vue-router": "^4.2.5"
},
"devDependencies": {
"@pinia/testing": "^0.1.3",
"@storybook/addon-actions": "^7.6.7",
"@storybook/addon-docs": "^7.6.7",
"@storybook/addon-essentials": "^7.6.7",
Expand Down
4 changes: 4 additions & 0 deletions src/api/currencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ export const addUserCurrencies = async (
) => (
api.post('/user/currencies', { currencies })
);

export const loadUserBaseCurrency = (): Promise<UserCurrencyModel> => (
api.get('/user/currencies/base')
);
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './monobank';
export * from './auth';
export * from './transactions';
export * from './stats';
export * from './currencies';
47 changes: 42 additions & 5 deletions src/components/fields/category-select-field.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<!-- eslint-disable vuejs-accessibility/aria-role -->
<template>
<div
:class="{
'category-select-field--disabled': $attrs.disabled,
'category-select-field--active': isDropdownOpened,
}"
class="category-select-field"
data-test="category-select-field"
role="select"
>
<FieldLabel
:label="label"
Expand All @@ -13,25 +16,31 @@
<div
class="category-select-field__wrapper"
>
<div
<button
v-bind="$attrs"
class="category-select-field__input"
ref="buttonRef"
class="button-style-reset category-select-field__input"
type="button"
:disabled="($attrs.disabled as boolean)"
aria-label="Select category"
:title="selectedValue?.name || 'Select category'"
@click="() => toggleDropdown()"
>
<template v-if="selectedValue">
<CategoryCircle :category="selectedValue" />
</template>
{{ selectedValue.name || placeholder }}
{{ selectedValue?.name || placeholder }}
<div class="category-select-field__arrow" />
</div>
</button>
<div
v-if="isDropdownOpened"
class="category-select-field__dropdown"
>
<div
ref="DOMList"
class="category-select-field__dropdown-values"
role="listbox"
>
<!-- Show top parent category at the top of list of child categories -->
<div class="category-select-field__search-field">
Expand All @@ -58,6 +67,8 @@
:class="{
'category-select-field__dropdown-item--highlighed': selectedValue.id === topLevelCategory.id,
}"
role="option"
:aria-selected="selectedValue.id === topLevelCategory.id"
@click="selectItem(topLevelCategory, true)"
>
<CategoryCircle :category="topLevelCategory" />
Expand Down Expand Up @@ -86,6 +97,8 @@
:class="{
'category-select-field__dropdown-item--highlighed': selectedValue.id === item.id,
}"
role="option"
:aria-selected="selectedValue.id === item.id"
@click="selectItem(item)"
>
<CategoryCircle :category="item" />
Expand All @@ -110,7 +123,9 @@
</template>
<script setup lang="ts">
import { ref, Ref, computed } from 'vue';
import {
ref, Ref, computed, watch, onBeforeUnmount,
} from 'vue';
import { CategoryModel } from 'shared-types';
import { type FormattedCategory } from '@/common/types';
import ChevronRightIcon from '@/assets/icons/chevron-right.svg?component';
Expand Down Expand Up @@ -140,6 +155,7 @@ const emit = defineEmits<{
'update:model-value': [value: FormattedCategory]
}>();
const selectedValue = ref(props.modelValue || props.values[0]);
const buttonRef = ref<HTMLButtonElement>(null);
const levelValues = ref(props.values);
Expand Down Expand Up @@ -169,6 +185,10 @@ const topLevelCategory = computed<FormattedCategory>(() => {
const toggleDropdown = (state?: boolean) => {
isDropdownOpened.value = state ?? !isDropdownOpened.value;
if (state === false) {
buttonRef.value.focus();
}
};
const filterCategories = (categories: FormattedCategory[], query: string): FormattedCategory[] => {
Expand Down Expand Up @@ -247,6 +267,23 @@ const backLevelUp = () => {
levelValues.value = level;
searchQuery.value = '';
};
const handleEscPress = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
toggleDropdown(false);
}
};
watch(isDropdownOpened, (value) => {
if (value) {
document.addEventListener('keydown', handleEscPress);
} else {
document.removeEventListener('keydown', handleEscPress);
}
});
onBeforeUnmount(() => {
document.removeEventListener('keydown', handleEscPress);
});
</script>
<style lang="scss">
Expand Down
40 changes: 35 additions & 5 deletions src/components/fields/select-field.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<!-- eslint-disable vuejs-accessibility/aria-role -->
<template>
<div
:class="{
'select-field--disabled': disabled,
'select-field--active': isDropdownOpened,
}"
role="select"
:aria-disabled="disabled"
class="select-field"
>
<template v-if="label">
Expand All @@ -14,15 +17,18 @@
v-click-outside="closeDropdown"
class="select-field__wrapper"
>
<div
<button
v-bind="$attrs"
class="select-field__input"
ref="buttonRef"
type="button"
:disabled="disabled"
class="button-style-reset select-field__input"
:title="modelValue ? getLabelFromValue(modelValue) : placeholder"
@click="toggleDropdown"
>
{{ modelValue ? getLabelFromValue(modelValue) : placeholder }}
<div class="select-field__arrow" />
</div>
</button>

<dropdown
:is-visible="isDropdownOpened"
Expand Down Expand Up @@ -56,7 +62,9 @@
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue';
import {
ref, computed, watch, onBeforeUnmount,
} from 'vue';
import Dropdown from '@/components/common/dropdown.vue';
import FieldError from './components/field-error.vue';
Expand Down Expand Up @@ -92,6 +100,7 @@ const emit = defineEmits<{
const isDropdownOpened = ref(false);
const filterQuery = ref('');
const buttonRef = ref<HTMLButtonElement>(null);
const getLabelFromValue = (value: ModelValue) => {
const { labelKey } = props;
Expand Down Expand Up @@ -120,7 +129,28 @@ const toggleDropdown = () => {
isDropdownOpened.value = !isDropdownOpened.value;
}
};
const closeDropdown = () => { isDropdownOpened.value = false; };
const closeDropdown = () => {
if (isDropdownOpened.value) {
isDropdownOpened.value = false;
buttonRef.value.focus();
}
};
const handleEscPress = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
closeDropdown();
}
};
watch(isDropdownOpened, (value) => {
if (value) {
document.addEventListener('keydown', handleEscPress);
} else {
document.removeEventListener('keydown', handleEscPress);
}
});
onBeforeUnmount(() => {
document.removeEventListener('keydown', handleEscPress);
});
const selectItem = ({ index }: { index: number }) => {
const { disabled } = props;
Expand Down
52 changes: 52 additions & 0 deletions src/components/modals/modify-record/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
INCOME_TRANSACTION,
EXPENSE_TRANSACTION,
COMMON_TRANSFER_TRANSACTION,
OUT_OF_WALLET_TRANSACTION,
} from '@tests/mocks';
import { TRANSACTION_TYPES } from 'shared-types';
import { getDestinationAmount, getFormTypeFromTransaction, getTxTypeFromFormType } from './helpers';
import { FORM_TYPES } from './types';

describe('components/modals/modify-record/helpers', () => {
describe('getDestinationAmount', () => {
test.each([
[[10, 20, false, false, INCOME_TRANSACTION], 10],
[[10, 20, false, true, INCOME_TRANSACTION], 10],
[[10, 20, true, false, INCOME_TRANSACTION], 20],

[[10, 20, false, false, EXPENSE_TRANSACTION], 10],
[[10, 20, false, true, EXPENSE_TRANSACTION], 20],
[[10, 20, true, false, EXPENSE_TRANSACTION], 20],
])('%s to be %s', (args, expected) => {
expect(getDestinationAmount({
fromAmount: args[0],
toAmount: args[1],
isCurrenciesDifferent: args[2],
isRecordExternal: args[3],
sourceTransaction: args[4],
})).toBe(expected);
});
});

describe('getFormTypeFromTransaction', () => {
test.each([
[INCOME_TRANSACTION, FORM_TYPES.income],
[EXPENSE_TRANSACTION, FORM_TYPES.expense],
[COMMON_TRANSFER_TRANSACTION, FORM_TYPES.transfer],
[OUT_OF_WALLET_TRANSACTION, FORM_TYPES.transfer],
])('%s to be %s', (value, expected) => {
expect(getFormTypeFromTransaction(value)).toBe(expected);
});
});

describe('getTxTypeFromFormType', () => {
test.each([
[FORM_TYPES.income, TRANSACTION_TYPES.income],
[FORM_TYPES.expense, TRANSACTION_TYPES.expense],
[FORM_TYPES.transfer, TRANSACTION_TYPES.expense],
])('%s to be %s', (value, expected) => {
expect(getTxTypeFromFormType(value)).toBe(expected);
});
});
});
2 changes: 2 additions & 0 deletions src/components/modals/modify-record/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export const getFormTypeFromTransaction = (tx: TransactionModel): FORM_TYPES =>
};

export const getTxTypeFromFormType = (formType: FORM_TYPES): TRANSACTION_TYPES => {
// When user creates a brand-new "transfer" transaction, it's always should be
// considered as "expense"
if (formType === FORM_TYPES.transfer) return TRANSACTION_TYPES.expense;

return formType === FORM_TYPES.expense
Expand Down
Loading

0 comments on commit b25898c

Please sign in to comment.