Browse Source

fix(cal): some more fixes

pull/7611/head
DarkPhoenix2704 9 months ago
parent
commit
d7d179a7e7
  1. 2
      packages/nc-gui/components/smartsheet/Toolbar.vue
  2. 22
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
  3. 4
      packages/nc-gui/components/smartsheet/calendar/MonthView.vue
  4. 5
      packages/nc-gui/components/smartsheet/calendar/SideMenu.vue
  5. 13
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue
  6. 2
      packages/nc-gui/components/smartsheet/calendar/index.vue
  7. 21
      packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue
  8. 2
      packages/nc-gui/composables/useSharedView.ts
  9. 2
      packages/nocodb/src/services/datas.service.ts
  10. 19
      tests/playwright/pages/Dashboard/ViewSidebar/index.ts
  11. 9
      tests/playwright/pages/Dashboard/common/Toolbar/CalendarRange.ts
  12. 54
      tests/playwright/pages/Dashboard/index.ts
  13. 320
      tests/playwright/tests/db/views/viewCalendar.spec.ts

2
packages/nc-gui/components/smartsheet/Toolbar.vue

@ -51,7 +51,7 @@ const { allowCSVDownload } = useSharedView()
<LazySmartsheetToolbarCalendarRange v-if="isCalendar" /> <LazySmartsheetToolbarCalendarRange v-if="isCalendar" />
<LazySmartsheetToolbarFieldsMenu <LazySmartsheetToolbarFieldsMenu
v-if="isGrid || isGallery || isKanban || isMap || !isCalendar" v-if="isGrid || isGallery || isKanban || isMap || isCalendar"
:show-system-fields="false" :show-system-fields="false"
/> />

22
packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue

@ -43,6 +43,8 @@ const recordsAcrossAllRange = computed<{
} }
} }
}>(() => { }>(() => {
if (!calendarRange.value || !formattedData.value) return { record: [], count: {} }
const scheduleStart = dayjs(selectedDate.value).startOf('day') const scheduleStart = dayjs(selectedDate.value).startOf('day')
const scheduleEnd = dayjs(selectedDate.value).endOf('day') const scheduleEnd = dayjs(selectedDate.value).endOf('day')
@ -56,8 +58,6 @@ const recordsAcrossAllRange = computed<{
const perRecordHeight = 80 const perRecordHeight = 80
if (!calendarRange.value) return { record: [], count: {} }
let recordsByRange: Array<Row> = [] let recordsByRange: Array<Row> = []
calendarRange.value.forEach((range) => { calendarRange.value.forEach((range) => {
@ -170,7 +170,7 @@ const recordsAcrossAllRange = computed<{
let _startDate = startDate.clone() let _startDate = startDate.clone()
while (_startDate.isBefore(endDate)) { while (_startDate.isBefore(endDate)) {
const timeKey = _startDate.format('HH:mm') const timeKey = _startDate.startOf('hour').format('HH:mm')
if (!overlaps[timeKey]) { if (!overlaps[timeKey]) {
overlaps[timeKey] = { overlaps[timeKey] = {
@ -192,10 +192,10 @@ const recordsAcrossAllRange = computed<{
_startDate = _startDate.add(15, 'minutes') _startDate = _startDate.add(15, 'minutes')
} }
const topInPixels = (startDate.hour() + startDate.minute() / 60) * 80 const topInPixels = (startDate.hour() + startDate.startOf('hour').minute() / 60) * 80
const heightInPixels = Math.max((endDate.diff(startDate, 'minute') / 60) * 80, perRecordHeight) const heightInPixels = Math.max((endDate.diff(startDate, 'minute') / 60) * 80, perRecordHeight)
const finalTopInPixels = topInPixels + startHour const finalTopInPixels = topInPixels + startHour * 2
style = { style = {
...style, ...style,
@ -227,7 +227,7 @@ const recordsAcrossAllRange = computed<{
} }
} }
const spacing = 1 const spacing = 0.25
const widthPerRecord = (100 - spacing * (maxOverlaps - 1)) / maxOverlaps const widthPerRecord = (100 - spacing * (maxOverlaps - 1)) / maxOverlaps
const leftPerRecord = (widthPerRecord + spacing) * overlapIndex const leftPerRecord = (widthPerRecord + spacing) * overlapIndex
@ -282,7 +282,7 @@ const onResize = (event: MouseEvent) => {
const ogEndDate = dayjs(resizeRecord.value.row[toCol.title!]) const ogEndDate = dayjs(resizeRecord.value.row[toCol.title!])
const ogStartDate = dayjs(resizeRecord.value.row[fromCol.title!]) const ogStartDate = dayjs(resizeRecord.value.row[fromCol.title!])
const hour = Math.floor(percentY * 24) const hour = Math.max(Math.min(Math.floor(percentY * 24), 23), 0)
if (resizeDirection.value === 'right') { if (resizeDirection.value === 'right') {
let newEndDate = dayjs(selectedDate.value).add(hour, 'hour') let newEndDate = dayjs(selectedDate.value).add(hour, 'hour')
@ -377,9 +377,9 @@ const onDrag = (event: MouseEvent) => {
if (!fromCol) return if (!fromCol) return
const hour = Math.floor(percentY * 24) const hour = Math.max(Math.min(Math.floor(percentY * 24), 23), 0)
const newStartDate = dayjs(selectedDate.value).add(hour, 'hour') const newStartDate = dayjs(selectedDate.value).startOf('day').add(hour, 'hour')
if (!newStartDate) return if (!newStartDate) return
@ -435,9 +435,9 @@ const stopDrag = (event: MouseEvent) => {
const fromCol = dragRecord.value.rowMeta.range?.fk_from_col const fromCol = dragRecord.value.rowMeta.range?.fk_from_col
const toCol = dragRecord.value.rowMeta.range?.fk_to_col const toCol = dragRecord.value.rowMeta.range?.fk_to_col
const hour = Math.floor(percentY * 24) const hour = Math.max(Math.min(Math.floor(percentY * 24), 23), 0)
const newStartDate = dayjs(selectedDate.value).add(hour, 'hour') const newStartDate = dayjs(selectedDate.value).startOf('day').add(hour, 'hour')
if (!newStartDate || !fromCol) return if (!newStartDate || !fromCol) return
let endDate let endDate

4
packages/nc-gui/components/smartsheet/calendar/MonthView.vue

@ -720,11 +720,11 @@ const isDateSelected = (date: dayjs.Dayjs) => {
v-for="(day, dateIndex) in week" v-for="(day, dateIndex) in week"
:key="`${weekIndex}-${dateIndex}`" :key="`${weekIndex}-${dateIndex}`"
:class="{ :class="{
'border-brand-500 border-1 border-r-1 border-b-1': 'border-brand-500 border-1 !border-r-1 border-b-1':
isDateSelected(day) || (focusedDate && dayjs(day).isSame(focusedDate, 'day')), isDateSelected(day) || (focusedDate && dayjs(day).isSame(focusedDate, 'day')),
'!text-gray-400': !isDayInPagedMonth(day), '!text-gray-400': !isDayInPagedMonth(day),
}" }"
class="text-right relative group text-sm h-full border-r-1 border-b-1 border-gray-200 font-medium hover:bg-gray-50 text-gray-800 bg-white" class="text-right relative group last:border-r-0 text-sm h-full border-r-1 border-b-1 border-gray-200 font-medium hover:bg-gray-50 text-gray-800 bg-white"
@click="selectDate(day)" @click="selectDate(day)"
> >
<div v-if="isUIAllowed('dataEdit')" class="flex justify-between p-1"> <div v-if="isUIAllowed('dataEdit')" class="flex justify-between p-1">

5
packages/nc-gui/components/smartsheet/calendar/SideMenu.vue

@ -209,6 +209,7 @@ const options = computed(() => {
return [ return [
{ label: 'In selected date', value: 'selectedDate' }, { label: 'In selected date', value: 'selectedDate' },
{ label: 'Without dates', value: 'withoutDates' }, { label: 'Without dates', value: 'withoutDates' },
{ label: 'In selected week', value: 'week' },
{ label: 'All records', value: 'allRecords' }, { label: 'All records', value: 'allRecords' },
] ]
} else { } else {
@ -323,8 +324,8 @@ onUnmounted(() => {
<component :is="iconMap.search" class="h-4 w-4 mr-1 text-gray-500" /> <component :is="iconMap.search" class="h-4 w-4 mr-1 text-gray-500" />
</template> </template>
</a-input> </a-input>
<NcSelect v-model:value="sideBarFilterOption" class="min-w-36"> <NcSelect v-model:value="sideBarFilterOption" class="min-w-36 !text-gray-800">
<a-select-option v-for="option in options" :key="option.value" :value="option.value"> <a-select-option v-for="option in options" :key="option.value" :value="option.value" class="!text-gray-800">
<NcTooltip :title="option.label" placement="top" show-on-truncate-only> <NcTooltip :title="option.label" placement="top" show-on-truncate-only>
<template #title>{{ option.label }}</template> <template #title>{{ option.label }}</template>
{{ option.label }} {{ option.label }}

13
packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue

@ -450,7 +450,7 @@ const onDrag = (event: MouseEvent) => {
if (!fromCol) return if (!fromCol) return
const day = Math.floor(percentX * 7) const day = Math.floor(percentX * 7)
const hour = Math.floor(percentY * 24) const hour = Math.max(Math.min(Math.floor(percentY * 24), 23), 0)
const newStartDate = dayjs(selectedDateRange.value.start).add(day, 'day').add(hour, 'hour') const newStartDate = dayjs(selectedDateRange.value.start).add(day, 'day').add(hour, 'hour')
if (!newStartDate) return if (!newStartDate) return
@ -510,7 +510,7 @@ const stopDrag = (event: MouseEvent) => {
const toCol = dragRecord.value.rowMeta.range?.fk_to_col const toCol = dragRecord.value.rowMeta.range?.fk_to_col
const day = Math.floor(percentX * 7) const day = Math.floor(percentX * 7)
const hour = Math.floor(percentY * 24) const hour = Math.max(Math.min(Math.floor(percentY * 24), 24), 0)
const newStartDate = dayjs(selectedDateRange.value.start).add(day, 'day').add(hour, 'hour') const newStartDate = dayjs(selectedDateRange.value.start).add(day, 'day').add(hour, 'hour')
if (!newStartDate || !fromCol) return if (!newStartDate || !fromCol) return
@ -729,6 +729,9 @@ const viewMore = (hour: dayjs.Dayjs) => {
<div <div
v-for="date in datesHours" v-for="date in datesHours"
:key="date[0].toISOString()" :key="date[0].toISOString()"
:class="{
'text-brand-500': date[0].isSame(dayjs(), 'date'),
}"
class="w-1/7 text-center text-sm text-gray-500 w-full py-1 border-gray-200 last:border-r-0 border-b-1 border-l-1 bg-gray-50" class="w-1/7 text-center text-sm text-gray-500 w-full py-1 border-gray-200 last:border-r-0 border-b-1 border-l-1 bg-gray-50"
> >
{{ dayjs(date[0]).format('DD ddd') }} {{ dayjs(date[0]).format('DD ddd') }}
@ -740,10 +743,10 @@ const viewMore = (hour: dayjs.Dayjs) => {
v-for="(hour, hourIndex) in date" v-for="(hour, hourIndex) in date"
:key="hourIndex" :key="hourIndex"
:class="{ :class="{
'border-1 !border-brand-500': hour.isSame(selectedTime, 'hour'), 'border-1 !border-brand-500 bg-gray-50': hour.isSame(selectedTime, 'hour'),
'!border-l-0': date[0].day() === selectedDateRange.start?.day(), '!border-l-0': date[0].day() === selectedDateRange.start?.day(),
}" }"
class="text-center relative h-20 text-sm text-gray-500 w-full py-1 border-gray-200 first:border-l-none border-1 border-r-gray-50 border-t-gray-50 bg-gray-50" class="text-center relative h-20 text-sm text-gray-500 w-full py-1 border-gray-200 first:border-l-none border-1 border-r-gray-50 border-t-gray-50"
@click=" @click="
() => { () => {
selectedTime = hour selectedTime = hour
@ -751,7 +754,7 @@ const viewMore = (hour: dayjs.Dayjs) => {
} }
" "
> >
<span v-if="date[0].day() === selectedDateRange.start?.day()" class="absolute left-1"> <span v-if="date[0].day() === selectedDateRange.start?.day()" class="absolute text-xs left-1">
{{ hour.format('h A') }} {{ hour.format('h A') }}
</span> </span>
<NcButton <NcButton

2
packages/nc-gui/components/smartsheet/calendar/index.vue

@ -165,7 +165,7 @@ const headerText = computed(() => {
</NcTooltip> </NcTooltip>
<NcDropdown v-model:visible="calendarRangeDropdown" :auto-close="false" :trigger="['click']"> <NcDropdown v-model:visible="calendarRangeDropdown" :auto-close="false" :trigger="['click']">
<NcButton :class="{ '!w-23': activeCalendarView === 'year' }" class="w-44" full-width size="small" type="secondary"> <NcButton :class="{ '!w-24': activeCalendarView === 'year' }" class="w-45" full-width size="small" type="secondary">
<div class="flex w-full px-3 py-1 w-full items-center justify-between"> <div class="flex w-full px-3 py-1 w-full items-center justify-between">
<span class="font-bold text-center text-brand-500">{{ headerText }}</span> <span class="font-bold text-center text-brand-500">{{ headerText }}</span>
<component :is="iconMap.arrowDown" class="h-4 w-4 text-gray-700" /> <component :is="iconMap.arrowDown" class="h-4 w-4 text-gray-700" />

21
packages/nc-gui/components/smartsheet/toolbar/CalendarRange.vue

@ -125,14 +125,20 @@ const removeRange = async (id: number) => {
</a-button> </a-button>
</div> </div>
<template #overlay> <template #overlay>
<div v-if="calendarRangeDropdown" class="w-full p-6 w-[35rem]" data-testid="nc-calendar-range-menu" @click.stop> <div v-if="calendarRangeDropdown" class="w-full p-6 w-[22rem]" data-testid="nc-calendar-range-menu" @click.stop>
<div v-for="(range, id) in _calendar_ranges" :key="id" class="flex w-full gap-2 mb-2 items-center"> <div
v-for="(range, id) in _calendar_ranges"
:key="id"
class="flex w-full gap-2 mb-2 items-center"
data-testid="nc-calendar-range-option"
>
<span> <span>
{{ $t('labels.organiseBy') }} {{ $t('labels.organiseBy') }}
</span> </span>
<NcSelect <NcSelect
v-model:value="range.fk_from_column_id" v-model:value="range.fk_from_column_id"
:placeholder="$t('placeholder.notSelected')" :placeholder="$t('placeholder.notSelected')"
data-testid="nc-calendar-range-from-field-select"
@change="saveCalendarRanges" @change="saveCalendarRanges"
> >
<a-select-option <a-select-option
@ -157,6 +163,7 @@ const removeRange = async (id: number) => {
<div <div
v-if="range.fk_to_column_id === null && isEeUI && false" v-if="range.fk_to_column_id === null && isEeUI && false"
class="flex cursor-pointer flex text-gray-800 items-center gap-1" class="flex cursor-pointer flex text-gray-800 items-center gap-1"
data-testid="nc-calendar-range-add-end-date"
@click=" @click="
() => { () => {
range.fk_to_column_id = undefined range.fk_to_column_id = undefined
@ -177,6 +184,7 @@ const removeRange = async (id: number) => {
:disabled="!range.fk_from_column_id" :disabled="!range.fk_from_column_id"
:placeholder="$t('placeholder.notSelected')" :placeholder="$t('placeholder.notSelected')"
class="!rounded-r-none nc-to-select" class="!rounded-r-none nc-to-select"
data-testid="nc-calendar-range-to-field-select"
@change="saveCalendarRanges" @change="saveCalendarRanges"
> >
<a-select-option <a-select-option
@ -215,7 +223,14 @@ const removeRange = async (id: number) => {
<component :is="iconMap.close" /> <component :is="iconMap.close" />
</NcButton> </NcButton>
</div> </div>
<NcButton v-if="false" class="mt-2" size="small" type="secondary" @click="addCalendarRange"> <NcButton
v-if="false"
class="mt-2"
data-testid="nc-calendar-range-add-btn"
size="small"
type="secondary"
@click="addCalendarRange"
>
<component :is="iconMap.plus" /> <component :is="iconMap.plus" />
Add another date field Add another date field
</NcButton> </NcButton>

2
packages/nc-gui/composables/useSharedView.ts

@ -121,7 +121,7 @@ export function useSharedView() {
nested?: any nested?: any
offset?: number offset?: number
}, },
headers: { headers?: {
ignorePagination?: boolean ignorePagination?: boolean
}, },
) => { ) => {

2
packages/nocodb/src/services/datas.service.ts

@ -33,7 +33,7 @@ export class DatasService {
view, view,
query: param.query, query: param.query,
throwErrorIfInvalidParams: true, throwErrorIfInvalidParams: true,
ignoreViewFilterAndSort: param.ignorePagination, ignorePagination: param.ignorePagination,
}); });
} }

19
tests/playwright/pages/Dashboard/ViewSidebar/index.ts

@ -12,6 +12,7 @@ export class ViewSidebarPage extends BasePage {
readonly createFormButton: Locator; readonly createFormButton: Locator;
readonly createKanbanButton: Locator; readonly createKanbanButton: Locator;
readonly createMapButton: Locator; readonly createMapButton: Locator;
readonly createCalendarButton: Locator;
readonly erdButton: Locator; readonly erdButton: Locator;
readonly apiSnippet: Locator; readonly apiSnippet: Locator;
@ -25,6 +26,7 @@ export class ViewSidebarPage extends BasePage {
this.createGridButton = this.get().locator('.nc-create-grid-view:visible'); this.createGridButton = this.get().locator('.nc-create-grid-view:visible');
this.createFormButton = this.get().locator('.nc-create-form-view:visible'); this.createFormButton = this.get().locator('.nc-create-form-view:visible');
this.createKanbanButton = this.get().locator('.nc-create-kanban-view:visible'); this.createKanbanButton = this.get().locator('.nc-create-kanban-view:visible');
this.createCalendarButton = this.get().locator('.nc-create-calendar-view:visible');
this.erdButton = this.get().locator('.nc-view-sidebar-erd'); this.erdButton = this.get().locator('.nc-view-sidebar-erd');
this.apiSnippet = this.get().locator('.nc-view-sidebar-api-snippet'); this.apiSnippet = this.get().locator('.nc-view-sidebar-api-snippet');
@ -54,12 +56,6 @@ export class ViewSidebarPage extends BasePage {
await this.rootPage.goto(this.rootPage.url()); await this.rootPage.goto(this.rootPage.url());
} }
private async createView({ title, type }: { title: string; type: ViewTypes }) {
await this.rootPage.waitForTimeout(1000);
await this.dashboard.sidebar.createView({ title, type });
}
async createGalleryView({ title }: { title: string }) { async createGalleryView({ title }: { title: string }) {
await this.createView({ title, type: ViewTypes.GALLERY }); await this.createView({ title, type: ViewTypes.GALLERY });
} }
@ -84,6 +80,11 @@ export class ViewSidebarPage extends BasePage {
await this.rootPage.waitForTimeout(1500); await this.rootPage.waitForTimeout(1500);
} }
async createCalendarView({ title }: { title: string }) {
await this.createView({ title, type: ViewTypes.CALENDAR });
await this.rootPage.waitForTimeout(1500);
}
async createMapView({ title }: { title: string }) { async createMapView({ title }: { title: string }) {
await this.createView({ title, type: ViewTypes.MAP }); await this.createView({ title, type: ViewTypes.MAP });
} }
@ -203,6 +204,12 @@ export class ViewSidebarPage extends BasePage {
// await expect(this.webhookButton).toHaveCount(count); // await expect(this.webhookButton).toHaveCount(count);
} }
private async createView({ title, type }: { title: string; type: ViewTypes }) {
await this.rootPage.waitForTimeout(1000);
await this.dashboard.sidebar.createView({ title, type });
}
// async openDeveloperTab({ option }: { option?: string }) { // async openDeveloperTab({ option }: { option?: string }) {
// await this.get().locator('.nc-tab').nth(1).click(); // await this.get().locator('.nc-tab').nth(1).click();
// if (option === 'ERD') { // if (option === 'ERD') {

9
tests/playwright/pages/Dashboard/common/Toolbar/CalendarRange.ts

@ -14,7 +14,14 @@ export class ToolbarCalendarRangePage extends BasePage {
} }
async click({ title }: { title: string }) { async click({ title }: { title: string }) {
await this.get().locator(`.nc-kanban-grouping-field-select`).click(); await (await this.get()).getByTestId('nc-calendar-range-from-field-select').click();
await this.rootPage.locator('.ant-select-dropdown:visible').locator(`div[title="${title}"]`).click(); await this.rootPage.locator('.ant-select-dropdown:visible').locator(`div[title="${title}"]`).click();
} }
async newCalendarRange({ fromTitle, toTitle }: { fromTitle: string; toTitle: string }) {
await this.get().locator(`.nc-calendar-range-from`).click();
await this.rootPage.locator('.ant-picker-cell-in-view').locator(`:text("${fromTitle}")`).click();
await this.get().locator(`.nc-calendar-range-to`).click();
await this.rootPage.locator('.ant-picker-cell-in-view').locator(`:text("${toTitle}")`).click();
}
} }

54
tests/playwright/pages/Dashboard/index.ts

@ -28,6 +28,7 @@ import { WorkspaceSettingsObject } from './WorkspaceSettings';
import { CmdJ } from './Command/CmdJPage'; import { CmdJ } from './Command/CmdJPage';
import { CmdK } from './Command/CmdKPage'; import { CmdK } from './Command/CmdKPage';
import { CmdL } from './Command/CmdLPage'; import { CmdL } from './Command/CmdLPage';
import { CalendarPage } from './Calendar';
export class DashboardPage extends BasePage { export class DashboardPage extends BasePage {
readonly base: any; readonly base: any;
@ -38,6 +39,7 @@ export class DashboardPage extends BasePage {
readonly treeView: TreeViewPage; readonly treeView: TreeViewPage;
readonly grid: GridPage; readonly grid: GridPage;
readonly gallery: GalleryPage; readonly gallery: GalleryPage;
readonly calendar: CalendarPage;
readonly form: FormPage; readonly form: FormPage;
readonly kanban: KanbanPage; readonly kanban: KanbanPage;
readonly map: MapPage; readonly map: MapPage;
@ -75,6 +77,7 @@ export class DashboardPage extends BasePage {
this.treeView = new TreeViewPage(this, base); this.treeView = new TreeViewPage(this, base);
this.grid = new GridPage(this); this.grid = new GridPage(this);
this.gallery = new GalleryPage(this); this.gallery = new GalleryPage(this);
this.calendar = new CalendarPage(this);
this.form = new FormPage(this); this.form = new FormPage(this);
this.kanban = new KanbanPage(this); this.kanban = new KanbanPage(this);
this.map = new MapPage(this); this.map = new MapPage(this);
@ -162,22 +165,6 @@ export class DashboardPage extends BasePage {
await expect(this.tabBar.locator(`.ant-tabs-tab:has-text("${title}")`)).not.toBeVisible(); await expect(this.tabBar.locator(`.ant-tabs-tab:has-text("${title}")`)).not.toBeVisible();
} }
private async _waitForDocsTabRender({ title, mode }: { title: string; mode: string }) {
await this.tabBar.locator(`.ant-tabs-tab-active:has-text("${title}")`).waitFor();
// wait active tab animation to finish
await expect
.poll(async () => {
return await this.tabBar.getByTestId(`nc-root-tabs-${title}`).evaluate(el => {
return window.getComputedStyle(el).getPropertyValue('color');
});
})
.toBe('rgb(67, 81, 232)');
await this.rootPage.waitForTimeout(500);
}
// When a tab is opened, it is not always immediately visible.
// Hence will wait till contents are visible // Hence will wait till contents are visible
async waitForTabRender({ async waitForTabRender({
title, title,
@ -189,6 +176,8 @@ export class DashboardPage extends BasePage {
type?: ProjectTypes; type?: ProjectTypes;
}) {} }) {}
// When a tab is opened, it is not always immediately visible.
async toggleMobileMode() { async toggleMobileMode() {
await this.baseMenuLink.click(); await this.baseMenuLink.click();
const projMenu = this.rootPage.locator('.nc-dropdown-base-menu'); const projMenu = this.rootPage.locator('.nc-dropdown-base-menu');
@ -251,26 +240,26 @@ export class DashboardPage extends BasePage {
await this.rootPage.locator('[data-testid="nc-loading"]').waitFor({ state: 'hidden' }); await this.rootPage.locator('[data-testid="nc-loading"]').waitFor({ state: 'hidden' });
} }
/* async closeAllTabs() { async closeAllTabs() {
await this.tabBar.locator(`.ant-tabs-tab`).waitFor({ state: 'visible' }); const tab = this.tabBar.locator(`.ant-tabs-tab`);
const tab = await this.tabBar.locator(`.ant-tabs-tab`);
const tabCount = await tab.count(); const tabCount = await tab.count();
for (let i = 0; i < tabCount; i++) { for (let i = 0; i < tabCount; i++) {
await tab.nth(i).locator('button.ant-tabs-tab-remove').click(); await tab.nth(i).locator('button.ant-tabs-tab-remove').click();
await tab.nth(i).waitFor({ state: 'detached' }); await this.rootPage.waitForTimeout(200);
}
} }
}*/
async closeAllTabs() { /* async closeAllTabs() {
const tab = this.tabBar.locator(`.ant-tabs-tab`); await this.tabBar.locator(`.ant-tabs-tab`).waitFor({ state: 'visible' });
const tab = await this.tabBar.locator(`.ant-tabs-tab`);
const tabCount = await tab.count(); const tabCount = await tab.count();
for (let i = 0; i < tabCount; i++) { for (let i = 0; i < tabCount; i++) {
await tab.nth(i).locator('button.ant-tabs-tab-remove').click(); await tab.nth(i).locator('button.ant-tabs-tab-remove').click();
await this.rootPage.waitForTimeout(200); await tab.nth(i).waitFor({ state: 'detached' });
}
} }
}*/
async validateWorkspaceMenu(param: { role: string; mode?: string }) { async validateWorkspaceMenu(param: { role: string; mode?: string }) {
await this.grid.workspaceMenu.toggle(); await this.grid.workspaceMenu.toggle();
@ -309,4 +298,19 @@ export class DashboardPage extends BasePage {
await this.grid.workspaceMenu.toggle(); await this.grid.workspaceMenu.toggle();
} }
private async _waitForDocsTabRender({ title, mode }: { title: string; mode: string }) {
await this.tabBar.locator(`.ant-tabs-tab-active:has-text("${title}")`).waitFor();
// wait active tab animation to finish
await expect
.poll(async () => {
return await this.tabBar.getByTestId(`nc-root-tabs-${title}`).evaluate(el => {
return window.getComputedStyle(el).getPropertyValue('color');
});
})
.toBe('rgb(67, 81, 232)');
await this.rootPage.waitForTimeout(500);
}
} }

320
tests/playwright/tests/db/views/viewCalendar.spec.ts

@ -0,0 +1,320 @@
import { test } from '@playwright/test';
import { DashboardPage } from '../../../pages/Dashboard';
import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import setup, { unsetup } from '../../../setup';
import { enableQuickRun, isPg, isSqlite } from '../../../setup/db';
import { TopbarPage } from '../../../pages/Dashboard/common/Topbar';
import { CalendarTopbarPage } from '../../../pages/Dashboard/Calendar/CalendarTopBar';
const filmRatings = ['G', 'PG', 'PG-13', 'R', 'NC-17'];
test.describe('View', () => {
let dashboard: DashboardPage, toolbar: ToolbarPage, topbar: TopbarPage, calendarTopbar: CalendarTopbarPage;
let context: any;
test.beforeEach(async ({ page }) => {
context = await setup({ page, isEmptyProject: false });
dashboard = new DashboardPage(page, context.base);
toolbar = toolbar = dashboard.calendar.toolbar;
topbar = dashboard.calendar.topbar;
calendarTopbar = dashboard.calendar.calendarTopbar;
// await dashboard.treeView.openTable({ title: 'Film' });
});
test.afterEach(async () => {
await unsetup(context);
});
test('Calendar', async () => {
/*await dashboard.viewSidebar.createKanbanView({
title: 'Film Kanban',
});
await dashboard.viewSidebar.verifyView({
title: 'Film Kanban',
index: 0,
});
// configure stack-by field
await toolbar.clickStackByField();
await toolbar.stackBy.click({ title: 'Rating' });
// menu overlaps the stack-by field; use escape key to close instead of clicking it explicitly
// click again to close menu
// await toolbar.clickStackByField();
await toolbar.rootPage.keyboard.press('Escape');
const kanban = dashboard.kanban;
await kanban.verifyStackCount({ count: 6 });
await kanban.verifyStackOrder({
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
});
await kanban.verifyStackFooter({
count: [0, 178, 194, 223, 195, 210],
});
await kanban.verifyCardCount({
count: [0, 25, 25, 25, 25, 25],
});
// hide fields
await toolbar.fields.toggleShowAllFields();
await toolbar.fields.toggleShowAllFields();
await toolbar.fields.toggle({ title: 'Title' });
await kanban.verifyCardCount({
count: [0, 25, 25, 25, 25, 25],
});
// verify card order
const order = [
['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'],
['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'],
['AIRPLANE SIERRA', 'ALABAMA DEVIL', 'ALTER VICTORY'],
['AIRPORT POLLOCK', 'ALONE TRIP', 'AMELIE HELLFIGHTERS'],
['ADAPTATION HOLES', 'ALADDIN CALENDAR', 'ALICE FANTASIA'],
];
for (let i = 1; i <= order.length; i++)
await kanban.verifyCardOrder({
stackIndex: i,
order: order[i - 1],
});
// // verify inter stack drag-drop
// await kanban.dragDropCard({
// from: "ACE GOLDFINGER",
// to: "ACADEMY DINOSAUR",
// });
// verify drag drop stack
await kanban.dragDropStack({
from: 1, // G
to: 2, // PG
});
await kanban.verifyStackOrder({
order: ['Uncategorized', 'PG', 'G', 'PG-13', 'R', 'NC-17'],
});
// verify drag drop stack
await kanban.dragDropStack({
from: 2, // G
to: 1, // PG
});
await kanban.verifyStackOrder({
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
});
// verify sort
await toolbar.sort.add({
title: 'Title',
ascending: false,
locallySaved: false,
});
// verify card order
const order2 = [
['YOUNG LANGUAGE', 'WEST LION'],
['WORST BANGER', 'WORDS HUNTER'],
];
for (let i = 1; i <= order2.length; i++)
await kanban.verifyCardOrder({
stackIndex: i,
order: order2[i - 1],
});
await toolbar.sort.reset();
// verify card order
const order3 = [
['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'],
['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'],
];
for (let i = 1; i <= order3.length; i++)
await kanban.verifyCardOrder({
stackIndex: i,
order: order3[i - 1],
});
// verify filter
await toolbar.clickFilter({
networkValidation: true,
});
await toolbar.filter.add({
title: 'Title',
operation: 'is like',
value: 'BA',
locallySaved: false,
});
await toolbar.clickFilter();
// verify card order
const order4 = [
['BAKED CLEOPATRA', 'BALLROOM MOCKINGBIRD'],
['ARIZONA BANG', 'EGYPT TENENBAUMS'],
];
for (let i = 1; i <= order4.length; i++)
await kanban.verifyCardOrder({
stackIndex: i,
order: order4[i - 1],
});
await toolbar.filter.reset();
const order5 = [
['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'],
['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'],
];
for (let i = 1; i <= order5.length; i++)
await kanban.verifyCardOrder({
stackIndex: i,
order: order5[i - 1],
});
await dashboard.rootPage.waitForTimeout(1000);*/
});
test('Calendar view operations', async () => {
/*test.slow();
await dashboard.viewSidebar.createKanbanView({
title: 'Film Kanban',
});
await dashboard.viewSidebar.verifyView({
title: 'Film Kanban',
index: 0,
});
await toolbar.sort.add({
title: 'Title',
ascending: false,
locallySaved: false,
});
await toolbar.clickFilter();
await toolbar.filter.add({
title: 'Title',
operation: 'is like',
value: 'BA',
locallySaved: false,
});
await toolbar.clickFilter();
await toolbar.fields.toggleShowAllFields();
await toolbar.fields.toggleShowAllFields();
await toolbar.fields.toggle({ title: 'Title' });
await dashboard.viewSidebar.copyView({ title: 'Film Kanban' });
await dashboard.viewSidebar.verifyView({
title: 'Kanban',
index: 1,
});
const kanban = dashboard.kanban;
await kanban.verifyStackCount({ count: 6 });
await kanban.verifyStackOrder({
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
});
await kanban.verifyStackFooter({
count: [0, 4, 5, 8, 6, 6],
});
await kanban.verifyCardCount({
count: [0, 4, 5, 8, 6, 6],
});
// verify card order
const order2 = [
['BAREFOOT MANCHURIAN', 'BARBARELLA STREETCAR'],
['WORST BANGER', 'PRESIDENT BANG'],
];
for (let i = 1; i <= order2.length; i++)
await kanban.verifyCardOrder({
stackIndex: i,
order: order2[i - 1],
});
await dashboard.viewSidebar.changeViewIcon({
title: 'Kanban',
icon: 'american-football',
iconDisplay: '🏈',
});
await dashboard.viewSidebar.deleteView({ title: 'Kanban' });
///////////////////////////////////////////////
await dashboard.viewSidebar.openView({ title: 'Film Kanban' });
// add new stack
await kanban.addNewStack({ title: 'Test' });
await dashboard.rootPage.waitForTimeout(1000);
await kanban.verifyStackCount({ count: 7 });
await kanban.verifyStackOrder({
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17', 'Test'],
});
// collapse stack
await kanban.verifyCollapseStackCount({ count: 0 });
await kanban.collapseStack({ index: 0 });
await kanban.verifyCollapseStackCount({ count: 1 });
await kanban.expandStack({ index: 0 });
await kanban.verifyCollapseStackCount({ count: 0 });
// add record to stack & verify
await toolbar.fields.toggleShowAllFields();
await toolbar.fields.toggleShowAllFields();
await toolbar.fields.toggleShowSystemFields();
await toolbar.fields.toggle({ title: 'LanguageId' });
await toolbar.fields.toggle({ title: 'Title' });
await toolbar.sort.reset();
await toolbar.filter.reset();
await kanban.addCard({ stackIndex: 6 });
await dashboard.expandedForm.fillField({
columnTitle: 'Title',
value: 'New record',
});
await dashboard.expandedForm.fillField({
columnTitle: 'LanguageId',
value: '1',
});
// todo: Check why kanban doesnt reload the rows data
await dashboard.expandedForm.save({ waitForRowsData: false });
// kludge: reload the page
await dashboard.rootPage.reload();
await kanban.verifyStackCount({ count: 7 });
await kanban.verifyStackOrder({
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17', 'Test'],
});
await kanban.verifyCardCount({
count: [0, 25, 25, 25, 25, 25, 1],
});
// delete stack
await kanban.deleteStack({ index: 6 });
await dashboard.rootPage.waitForTimeout(1000);
await kanban.verifyStackCount({ count: 6 });
await kanban.verifyStackOrder({
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'],
});
await kanban.verifyCardCount({
count: [1, 25, 25, 25, 25, 25],
});*/
});
test.skip('Calendar shared view operations', async ({ page }) => {
test.slow();
/*
await dashboard.viewSidebar.createKanbanView({
title: 'Film Kanban',
});
await dashboard.viewSidebar.verifyView({
title: 'Film Kanban',
index: 0,
});
// Share view
await toolbar.fields.toggle({ title: 'Rating' });
const sharedLink = await topbar.getSharedViewUrl();
// await toolbar.shareView.close();
// sign-out
await dashboard.signOut();
// Open shared view & verify stack count
await page.goto(sharedLink);
await page.reload();
const kanban = dashboard.kanban;
await kanban.verifyStackCount({ count: 6 });*/
});
});
Loading…
Cancel
Save