Browse Source

test: add playwright test

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/4406/head
Pranav C 2 years ago
parent
commit
b4b63e5140
  1. 76
      packages/nc-gui/components/cell/MultiSelect.vue
  2. 72
      packages/nc-gui/components/cell/SingleSelect.vue
  3. 33
      tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts
  4. 25
      tests/playwright/tests/columnMultiSelect.spec.ts
  5. 13
      tests/playwright/tests/columnSingleSelect.spec.ts

76
packages/nc-gui/components/cell/MultiSelect.vue

@ -8,14 +8,17 @@ import {
IsKanbanInj,
ReadonlyInj,
computed,
enumColor,
h,
inject,
onMounted,
reactive,
ref,
useEventListener,
useMetas,
useProject,
useSelectedCellKeyupListener,
watch, enumColor, useMetas,
watch,
} from '#imports'
import MdiCloseCircle from '~icons/mdi/close-circle'
@ -28,8 +31,6 @@ const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const emptyOption = Symbol('emptyOption')
const { isMysql } = useProject()
const column = inject(ColumnInj)!
@ -54,11 +55,7 @@ const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const tempVal = ref<string>()
const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value)
})
const tempVal = reactive([])
const options = computed<(SelectOptionType & { value?: string })[]>(() => {
if (column?.value.colOptions) {
@ -73,6 +70,10 @@ const options = computed<(SelectOptionType & { value?: string })[]>(() => {
return []
})
const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value)
})
const vModel = computed({
get: () => {
const selected = selectedIds.value.reduce((acc, id) => {
@ -83,7 +84,7 @@ const vModel = computed({
return acc
}, [] as string[])
if (tempVal.value) selected.push(tempVal.value)
if (tempVal.length) selected.push(...tempVal)
return selected
},
@ -112,18 +113,18 @@ const selectedTitles = computed(() =>
: [],
)
const handleKeys = async (e: KeyboardEvent) => {
switch (e.key) {
case 'Escape':
e.preventDefault()
isOpen.value = false
break
case 'Enter':
e.stopPropagation()
await addIfMissingAndSave()
break
}
}
// const handleKeys = async (e: KeyboardEvent) => {
// switch (e.key) {
// case 'Escape':
// e.preventDefault()
// isOpen.value = false
// break
// case 'Enter':
// e.stopPropagation()
// await addIfMissingAndSave()
// break
// }
// }
const handleClose = (e: MouseEvent) => {
if (aselect.value && !aselect.value.$el.contains(e.target)) {
@ -177,20 +178,26 @@ useSelectedCellKeyupListener(active, (e) => {
isOpen.value = true
}
break
default:
isOpen.value = true
break
}
})
const activeOptCreateInProgress = ref(0)
async function addIfMissingAndSave() {
if (!searchVal) return false
try {
tempVal.value = searchVal.value
tempVal.push(searchVal.value)
const newOptValue = searchVal?.value
searchVal.value = ''
activeOptCreateInProgress.value++
if (newOptValue && !options.value.some((o) => o.title === newOptValue)) {
const newOptions = [...options.value]
newOptions.push({
title: newOptValue, value: newOptValue,
title: newOptValue,
value: newOptValue,
color: enumColor.light[(options.value.length + 1) % enumColor.light.length],
})
column.value.colOptions = { options: newOptions.map(({ value: _, ...rest }) => rest) }
@ -198,20 +205,25 @@ async function addIfMissingAndSave() {
await $api.dbTableColumn.update(column.value?.id as string, {
...column.value,
})
await getMeta(column.value.fk_model_id!, true)
vModel.value = [...vModel.value]
searchVal.value = ''
activeOptCreateInProgress.value--
if (!activeOptCreateInProgress.value) {
await getMeta(column.value.fk_model_id!, true)
vModel.value = [...vModel.value];
tempVal.splice(0, tempVal.length);
}
} else {
activeOptCreateInProgress.value--
}
} catch (e) {
// todo: handle error message
console.log(e)
activeOptCreateInProgress.value--
} finally {
tempVal.value = ''
// tempVal.value = ''
}
}
const search = () => {
searchVal.value = aselect.value?.$el?.querySelector('.ant-select-selection-search-input')?.value
}
@ -226,7 +238,7 @@ const search = () => {
class="w-full"
:bordered="false"
:show-arrow="!readOnly"
show-search
:show-search="active || editable"
:open="isOpen"
:disabled="readOnly"
:class="{ '!ml-[-8px]': readOnly }"
@ -260,7 +272,9 @@ const search = () => {
<a-select-option v-if="searchVal && isOptionMissing" :key="searchVal" :value="searchVal">
<div class="flex gap-2 text-gray-500 items-center">
<MdiPlusThick class="min-w-4" />
<div class="text-xs whitespace-normal"> Create new option named <strong>{{ searchVal }}</strong></div>
<div class="text-xs whitespace-normal">
Create new option named <strong>{{ searchVal }}</strong>
</div>
</div>
</a-select-option>

72
packages/nc-gui/components/cell/SingleSelect.vue

@ -10,11 +10,11 @@ import {
IsKanbanInj,
ReadonlyInj,
computed,
enumColor,
inject,
ref,
useEventListener,
watch,
enumColor,
} from '#imports'
interface Props {
@ -45,6 +45,20 @@ const { $api } = useNuxtApp()
const searchVal = ref()
const tempVal = ref<string>()
const options = computed<(SelectOptionType & { value: string })[]>(() => {
if (column?.value.colOptions) {
const opts = column.value.colOptions
? // todo: fix colOptions type, options does not exist as a property
(column.value.colOptions as any).options.filter((el: SelectOptionType) => el.title !== '') || []
: []
for (const op of opts.filter((el: any) => el.order === null)) {
op.title = op.title.replace(/^'/, '').replace(/'$/, '')
}
return opts.map((o: any) => ({ ...o, value: o.title }))
}
return []
})
const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value)
})
@ -62,36 +76,21 @@ const vModel = computed({
},
})
const options = computed<(SelectOptionType & { value: string })[]>(() => {
if (column?.value.colOptions) {
const opts = column.value.colOptions
? // todo: fix colOptions type, options does not exist as a property
(column.value.colOptions as any).options.filter((el: SelectOptionType) => el.title !== '') || []
: []
for (const op of opts.filter((el: any) => el.order === null)) {
op.title = op.title.replace(/^'/, '').replace(/'$/, '')
}
return opts.map((o: any) => ({ ...o, value: o.title }))
}
return []
})
const handleKeys = async (e: KeyboardEvent) => {
switch (e.key) {
case 'Escape':
e.preventDefault()
isOpen.value = false
break
case 'Enter':
e.preventDefault()
// if (await addIfMissingAndSave())
// e.stopPropagation()
break
}
}
const handleClose = (e: MouseEvent) => {
// const handleKeys = async (e: KeyboardEvent) => {
// switch (e.key) {
// case 'Escape':
// e.preventDefault()
// isOpen.value = false
// break
// case 'Enter':
// e.preventDefault()
// // if (await addIfMissingAndSave())
// // e.stopPropagation()
// break
// }
// }
const handleClose = (_e: MouseEvent) => {
// if (aselect.value && !aselect.value.$el.contains(e.target)) {
// isOpen.value = false
// aselect.value.blur()
@ -121,9 +120,7 @@ useSelectedCellKeyupListener(active, (e) => {
}
})
async function addIfMissingAndSave() {
if (!searchVal.value) return false
const newOptValue = searchVal.value
@ -144,7 +141,6 @@ async function addIfMissingAndSave() {
}
}
const search = () => {
searchVal.value = aselect.value?.$el?.querySelector('.ant-select-selection-search-input')?.value
}
@ -161,9 +157,9 @@ const search = () => {
:disabled="readOnly"
:show-arrow="!readOnly && (active || editable || vModel === null)"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen ? 'active' : ''}`"
:show-search="active || editable"
@select="isOpen = false"
@keydown.stop
show-search
@search="search"
@click="isOpen = (active || editable) && !isOpen"
>
@ -189,14 +185,14 @@ const search = () => {
</a-tag>
</a-select-option>
<a-select-option v-if="searchVal && isOptionMissing" :key="searchVal" :value="searchVal">
<div class="flex gap-2 text-gray-500 items-center">
<MdiPlusThick class="min-w-4" />
<div class="text-xs whitespace-normal"> Create new option named <strong>{{ searchVal }}</strong></div>
<div class="text-xs whitespace-normal">
Create new option named <strong>{{ searchVal }}</strong>
</div>
</div>
</a-select-option>
</a-select>
</template>

33
tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts

@ -112,4 +112,37 @@ export class SelectOptionCellPageObject extends BasePage {
await this.get({ index, columnHeader }).click();
await this.rootPage.locator(`.nc-dropdown-single-select-cell`).nth(index).waitFor({ state: 'hidden' });
}
async addNewOption({
index,
columnHeader,
option,
multiSelect,
}: {
index: number;
columnHeader: string;
option: string;
multiSelect?: boolean;
}) {
const selectCell = this.get({ index, columnHeader });
// check if cell active
if (!(await selectCell.getAttribute('class')).includes('active')) {
await selectCell.click();
}
await selectCell.locator('.ant-select-selection-search-input').type(option);
await selectCell.locator('.ant-select-selection-search-input').press('Enter');
if (multiSelect) await selectCell.locator('.ant-select-selection-search-input').press('Escape');
// await this.rootPage.getByTestId(`select-option-${columnHeader}-${index}`).getByText(option).click();
//
//
// await this.rootPage
// .getByTestId(`select-option-${columnHeader}-${index}`)
// .getByText(option)
// .waitFor({ state: 'hidden' });
}
}

25
tests/playwright/tests/columnMultiSelect.spec.ts

@ -128,4 +128,29 @@ test.describe('Multi select', () => {
await grid.column.delete({ title: 'MultiSelect' });
});
test.only('Add new option directly from cell', async () => {
await grid.cell.selectOption.addNewOption({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option added from cell 1',
multiSelect: true,
});
await grid.cell.selectOption.addNewOption({
index: 0,
columnHeader: 'MultiSelect',
option: 'Option added from cell 2',
multiSelect: true,
});
await grid.cell.selectOption.verifyOptions({
index: 0,
columnHeader: 'MultiSelect',
options: ['Option added from cell 1', 'Option added from cell 2'],
});
await new Promise(resolve => setTimeout(resolve, 100000));
await grid.column.delete({ title: 'SingleSelect' });
});
});

13
tests/playwright/tests/columnSingleSelect.spec.ts

@ -69,4 +69,17 @@ test.describe('Single select', () => {
await grid.column.delete({ title: 'SingleSelect' });
});
test('Add new option directly from cell', async () => {
await grid.cell.selectOption.addNewOption({
index: 0,
columnHeader: 'SingleSelect',
option: 'Option added from cell',
});
await grid.cell.selectOption.verify({ index: 0, columnHeader: 'SingleSelect', option: 'Option added from cell' });
await new Promise(resolve => setTimeout(resolve, 100000));
await grid.column.delete({ title: 'SingleSelect' });
});
});

Loading…
Cancel
Save