Browse Source

Merge branch 'develop' into qr-prototyping-cleanedup

* plus extract out AllowedColumnTypesForQrCode into sdk
pr-4468-qr-code-extraction
Daniel Spaude 2 years ago
parent
commit
a3d06eb845
No known key found for this signature in database
GPG Key ID: 654A3D1FA4F35FFE
  1. 5
      .github/workflows/ci-cd.yml
  2. 1
      .github/workflows/dco-check.yml
  3. 7
      .github/workflows/release-nocodb.yml
  4. 6
      .gitignore
  5. 23
      markdown/readme/languages/chinese.md
  6. 24
      markdown/readme/languages/german.md
  7. 2174
      package-lock.json
  8. 19
      package.json
  9. 12
      packages/nc-cli/package-lock.json
  10. 2
      packages/nc-gui/app.vue
  11. 2
      packages/nc-gui/components/cell/MultiSelect.vue
  12. 2
      packages/nc-gui/components/cell/SingleSelect.vue
  13. 7
      packages/nc-gui/components/smartsheet/Gallery.vue
  14. 7
      packages/nc-gui/components/smartsheet/Kanban.vue
  15. 33
      packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue
  16. 15
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  17. 1
      packages/nc-gui/components/smartsheet/header/CellIcon.ts
  18. 27
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  19. 21
      packages/nc-gui/components/virtual-cell/Lookup.vue
  20. 34
      packages/nc-gui/composables/useFieldQuery.ts
  21. 19
      packages/nc-gui/composables/useSmartsheetStore.ts
  22. 74
      packages/nc-gui/lang/vi.json
  23. 2
      packages/nc-gui/layouts/default.vue
  24. 4
      packages/nc-gui/package-lock.json
  25. 5
      packages/nc-gui/pages/account/index/users.vue
  26. 30
      packages/nc-gui/pages/account/index/users/[[nestedPage]].vue
  27. 156
      packages/nc-gui/pages/index/index/index.vue
  28. 2
      packages/nc-lib-gui/package.json
  29. 12
      packages/nc-plugin/package-lock.json
  30. 224
      packages/noco-docs/content/en/engineering/playwright.md
  31. 3
      packages/noco-docs/content/en/engineering/translation.md
  32. 161
      packages/noco-docs/content/en/engineering/unit-testing.md
  33. 12
      packages/noco-docs/package-lock.json
  34. 4
      packages/nocodb-sdk/package-lock.json
  35. 2
      packages/nocodb-sdk/package.json
  36. 1
      packages/nocodb-sdk/src/index.ts
  37. 10
      packages/nocodb-sdk/src/lib/columnRules/QrCodeRules.ts
  38. 1
      packages/nocodb-sdk/src/lib/columnRules/index.ts
  39. 15
      packages/nocodb/docker-compose.yml
  40. 359
      packages/nocodb/package-lock.json
  41. 13
      packages/nocodb/package.json
  42. 120
      packages/nocodb/src/lib/db/sql-client/lib/mssql/MssqlClient.ts
  43. 65
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts
  44. 7
      packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerColumnMetas.ts
  45. 21
      packages/nocodb/src/lib/meta/api/swagger/helpers/swagger-base.json
  46. 9
      packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts
  47. 14
      packages/nocodb/src/lib/models/Column.ts
  48. 6
      packages/nocodb/src/lib/models/SelectOption.ts
  49. 53
      scripts/cypress/cypress.json
  50. 67
      scripts/cypress/docker-compose-cypress.yml
  51. 17
      scripts/cypress/docker-compose-pg-cy-quick.yml
  52. 17
      scripts/cypress/docker-compose-pg.yml
  53. 5
      scripts/cypress/fixtures/example.json
  54. BIN
      scripts/cypress/fixtures/images/avatar-1.JPG
  55. BIN
      scripts/cypress/fixtures/images/avatar-2.JPG
  56. BIN
      scripts/cypress/fixtures/images/avatar-3.JPG
  57. BIN
      scripts/cypress/fixtures/images/avatar-4.JPG
  58. BIN
      scripts/cypress/fixtures/images/avatar-5.JPG
  59. BIN
      scripts/cypress/fixtures/images/avatar-6.JPG
  60. 5
      scripts/cypress/fixtures/sampleFiles/1.json
  61. 4
      scripts/cypress/fixtures/sampleFiles/2.json
  62. 4
      scripts/cypress/fixtures/sampleFiles/3.json
  63. 4
      scripts/cypress/fixtures/sampleFiles/4.json
  64. 4
      scripts/cypress/fixtures/sampleFiles/5.json
  65. 4
      scripts/cypress/fixtures/sampleFiles/6.json
  66. BIN
      scripts/cypress/fixtures/sampleFiles/Financial Sample.xlsx
  67. 16
      scripts/cypress/fixtures/sampleFiles/iFrame.html
  68. BIN
      scripts/cypress/fixtures/sampleFiles/sample.xlsx
  69. BIN
      scripts/cypress/fixtures/sampleFiles/simple.xlsx
  70. 1250
      scripts/cypress/fixtures/sqlite-sakila/regenFiles/index.js
  71. 854
      scripts/cypress/fixtures/sqlite-sakila/regenFiles/package-lock.json
  72. 14
      scripts/cypress/fixtures/sqlite-sakila/regenFiles/package.json
  73. 6
      scripts/cypress/fixtures/sqlite-sakila/regenFiles/recreate_sakila_sqlite.sh
  74. 45
      scripts/cypress/fixtures/sqlite-sakila/regenFiles/sqlite-sakila-delete-data.sql
  75. 70
      scripts/cypress/fixtures/sqlite-sakila/regenFiles/sqlite-sakila-drop-objects.sql
  76. 231504
      scripts/cypress/fixtures/sqlite-sakila/regenFiles/sqlite-sakila-insert-data.sql
  77. 647
      scripts/cypress/fixtures/sqlite-sakila/regenFiles/sqlite-sakila-schema.sql
  78. BIN
      scripts/cypress/fixtures/sqlite-sakila/sakila.db
  79. 261
      scripts/cypress/integration/common/00_pre_configurations.js
  80. 106
      scripts/cypress/integration/common/1a_table_operations.js
  81. 200
      scripts/cypress/integration/common/1b_table_column_operations.js
  82. 143
      scripts/cypress/integration/common/1c_sql_view.js
  83. 147
      scripts/cypress/integration/common/1d_pg_table_view_drag_drop_reorder.js
  84. 156
      scripts/cypress/integration/common/1d_table_view_drag_drop_reorder.js
  85. 169
      scripts/cypress/integration/common/1e_meta_sync.js
  86. 182
      scripts/cypress/integration/common/1e_pg_meta_sync.js
  87. 118
      scripts/cypress/integration/common/2a_table_with_belongs_to_colulmn.js
  88. 130
      scripts/cypress/integration/common/2b_table_with_m2m_column.js
  89. 198
      scripts/cypress/integration/common/3a_filter_sort_fields_operations.js
  90. 369
      scripts/cypress/integration/common/3b_formula_column.js
  91. 110
      scripts/cypress/integration/common/3c_lookup_column.js
  92. 147
      scripts/cypress/integration/common/3d_rollup_column.js
  93. 296
      scripts/cypress/integration/common/3e_duration_column.js
  94. 380
      scripts/cypress/integration/common/3f_link_to_another_record.js
  95. 95
      scripts/cypress/integration/common/4a_table_view_grid_gallery_form.js
  96. 120
      scripts/cypress/integration/common/4b_table_view_share.js
  97. 406
      scripts/cypress/integration/common/4c_form_view_detailed.js
  98. 107
      scripts/cypress/integration/common/4d_table_view_grid_locked.js
  99. 226
      scripts/cypress/integration/common/4e_form_view_share.js
  100. 477
      scripts/cypress/integration/common/4f_grid_view_share.js
  101. Some files were not shown because too many files have changed in this diff Show More

5
.github/workflows/ci-cd.yml

@ -8,7 +8,6 @@ on:
branches: [develop]
paths:
- "packages/nc-gui/**"
- "scripts/cypress/**"
- "packages/nocodb/**"
- ".github/workflows/ci-cd.yml"
pull_request:
@ -16,7 +15,6 @@ on:
branches: [develop]
paths:
- "packages/nc-gui/**"
- "scripts/cypress/**"
- "packages/nocodb/**"
- ".github/workflows/ci-cd.yml"
@ -60,9 +58,6 @@ jobs:
- name: Install dependencies
working-directory: ./packages/nocodb
run: npm install
- name: setup mysql
working-directory: ./
run: docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up -d
- name: run unit tests
working-directory: ./packages/nocodb
run: npm run test:unit

1
.github/workflows/dco-check.yml

@ -10,7 +10,6 @@ on:
- "packages/nc-lib-gui/**"
- "packages/nc-plugin/**"
- "packages/nocodb/**"
- "scripts/cypress/**"
jobs:
commits_check_job:

7
.github/workflows/release-nocodb.yml

@ -117,12 +117,7 @@ jobs:
secrets:
GH_TOKEN: "${{ secrets.GH_TOKEN }}"
# Change nocodb-sdk back to local path
update-sdk-path:
needs: publish-docs
uses: ./.github/workflows/update-sdk-path.yml
# Sync changes to develop
sync-to-develop:
needs: update-sdk-path
needs: close-issues
uses: ./.github/workflows/sync-to-develop.yml

6
.gitignore vendored

@ -84,12 +84,6 @@ mongod
/packages/nocodb/docker/main.js.LICENSE.txt
/packages/nocodb/noco_log.db
# Cypress
#=========
shared.json
/scripts/Cypress/screenshots
/scripts/exp/
# NC_DBs
#=========
nc_minimal_dbs/

23
markdown/readme/languages/chinese.md

@ -255,29 +255,6 @@ npm run dev
> nocodb/packages/nocodb 包括 nc-lib-gui,它是 npm 源中托管的 nc-gui 的预构建版本。如果您只想修改后端,则可以在本地启动后端后在浏览器中访问 localhost:8000/dashboard
## 本地运行 Cypress 测试
```shell
# 安装依赖 (cypress)
npm install
# 使用 docker compose 运行带有所需数据库的 mysql 数据库
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# 使用以下命令运行后端 api
npm run start:api
# 使用以下命令运行前端 Web UI
npm run start:web
# 等待 3000 和 8080 端口都可用后
# 使用以下命令运行 cypress 测试
npm run cypress:run
# 或运行以下命令以使用 GUI 运行它
npm run cypress:open
```
# 贡献
参见[贡献指南](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md).

24
markdown/readme/languages/german.md

@ -236,30 +236,6 @@ npm run dev
> nocodb/packages/nocodb enthält nc-lib-gui, die entwickelte Version von nc-gui, die in der npm-Registry gehostet wird. Sie können localhost:8000/dashboard im Browser aufrufen, nachdem Sie das Backend lokal gestartet haben, wenn Sie nur das Backend ändern möchten.
## Cypress-Tests lokal ausführen
```shell
# install dependencies (cypress)
npm install
# MySQL-Datenbank mit der benötigten Datenbank mit Docker Compose ausführen
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# Backend API mit folgendem Befehl ausführen
npm run start:api
# Frontend Web-UI mit folgendem Befehl ausführen
npm run start:web
# Warten, bis die beiden Ports 3000 und 8000 verfügbar sind,
# dann Cypress Test mit diesem Befehl ausführen
npm run cypress:run
# Oder diesen Befehl ausführen, um die GUI auszuführen
npm run cypress:open
```
# Beiträge
Siehe [Contribution Guide](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md).

2174
package-lock.json generated

File diff suppressed because it is too large Load Diff

19
package.json

@ -16,10 +16,6 @@
},
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@4tw/cypress-drag-drop": "^2.0.0",
"cypress": "^9.2.0",
"cypress-file-upload": "^5.0.8",
"cypress-iframe": "^1.0.1",
"fs": "0.0.1-security",
"lerna": "^3.20.1",
"husky": "^8.0.0",
@ -39,25 +35,16 @@
"lint:staged:playwright": "cd ./tests/playwright; npx lint-staged; cd -",
"build:common": "cd ./packages/nocodb-sdk; npm install; npm run build",
"install:common": "cd ./packages/nocodb; npm install ../nocodb-sdk; cd ../nc-gui; npm install ../nocodb-sdk",
"start:api": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_CACHE=true NC_DISABLE_TELE=true npm run watch:run:cypress",
"start:xcdb-api": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk;npm install; NC_EXPORT_MAX_TIMEOUT=60000 NC_DISABLE_CACHE=true NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress",
"start:api:cache": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk;npm install; NC_EXPORT_MAX_TIMEOUT=60000 NC_DISABLE_TELE=true npm run watch:run:cypress",
"start:api:cache:pg": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress:pg",
"start:api:cache:pg:cyquick": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_DISABLE_TELE=true npm run watch:run:cypress:pg:cyquick",
"start:xcdb-api:cache": "npm run build:common ; cd ./packages/nocodb; npm install ../nocodb-sdk; npm install; NC_EXPORT_MAX_TIMEOUT=60000 NC_DISABLE_TELE=true NC_INFLECTION=camelize DATABASE_URL=sqlite:../../../scripts/cypress/fixtures/sqlite-sakila/sakila.db npm run watch:run:cypress",
"start:web": "npm run build:common ; cd ./packages/nc-gui; npm install ../nocodb-sdk; npm install; ANT_MESSAGE_DURATION=0.5 npm run dev",
"cypress:run": "cypress run --config-file ./scripts/cypress/cypress.json",
"cypress:open": "cypress open --config-file ./scripts/cypress/cypress.json",
"cypress:clear": "cypress cache clear",
"test:travis": "git log --pretty=format:'%h' -n 1 --skip 1 | xargs lerna run test:travis --since",
"lerna:install": "git log --pretty=format:'%h' -n 1 --skip 1 | xargs lerna bootstrap --ignore nc-cli --since",
"updated:xc-migrator": "lerna run publish --scope xc-migrator && lerna run xc && lerna publish && npm install -f xc-cli",
"doc": "lerna run doc",
"install:local:dep": "cd packages/nc-lib-gui;npm uninstall -S xc-lib;rm package-lock.json; npm i ../../../xc-lib-private; cd ../xc-instant;npm uninstall -S xc-lib xc-lib-gui;npm i ../../../xc-lib-private;npm i ../xc-lib-gui",
"install:npm:dep": "cd packages/nc-lib-gui;npm uninstall -S xc-lib; npm i -S xc-lib@latest; cd ../xc-instant;npm uninstall -S xc-lib xc-lib-gui;npm i -S xc-lib@latest xc-lib-gui@latest;npm i ../xc-lib-gui",
"start:pg": "docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d",
"stop:pg": "docker-compose -f ./scripts/cypress/docker-compose-pg.yml down",
"prepare": "husky install"
"prepare": "husky install",
"start:pg": "docker-compose -f ./tests/playwright/scripts/docker-compose-pg.yml up -d",
"stop:pg": "docker-compose -f ./tests/playwright/scripts/docker-compose-pg.yml down"
},
"dependencies": {
"express": "^4.18.1",

12
packages/nc-cli/package-lock.json generated

@ -9465,9 +9465,9 @@
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -22773,9 +22773,9 @@
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}

2
packages/nc-gui/app.vue

@ -36,7 +36,7 @@ if (typeof window !== 'undefined') {
<template>
<a-config-provider>
<NuxtLayout :name="disableBaseLayout ? false : 'base'">
<NuxtPage :key="key" />
<NuxtPage :key="key" :transition="false" />
</NuxtLayout>
</a-config-provider>
</template>

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

@ -271,8 +271,8 @@ const onTagClick = (e: Event, onClose: Function) => {
class="w-full"
:bordered="false"
clear-icon
show-search
:show-arrow="!readOnly"
:show-search="active || editable"
:open="isOpen && (active || editable)"
:disabled="readOnly"
:class="{ '!ml-[-8px]': readOnly }"

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

@ -189,7 +189,7 @@ const toggleMenu = (e: Event) => {
:disabled="readOnly"
:show-arrow="!readOnly && (active || editable || vModel === null)"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen ? 'active' : ''}`"
:show-search="active || editable"
show-search
@select="isOpen = false"
@keydown.stop
@search="search"

7
packages/nc-gui/components/smartsheet/Gallery.vue

@ -80,7 +80,12 @@ const isRowEmpty = (record: any, col: any) => {
const attachments = (record: any): Attachment[] => {
try {
return coverImageColumn?.title && record.row[coverImageColumn.title] ? JSON.parse(record.row[coverImageColumn.title]) : []
if (coverImageColumn?.title && record.row[coverImageColumn.title]) {
return typeof record.row[coverImageColumn.title] === 'string'
? JSON.parse(record.row[coverImageColumn.title])
: record.row[coverImageColumn.title]
}
return []
} catch (e) {
return []
}

7
packages/nc-gui/components/smartsheet/Kanban.vue

@ -116,7 +116,12 @@ reloadViewDataHook?.on(async () => {
const attachments = (record: any): Attachment[] => {
try {
return coverImageColumn?.title && record.row[coverImageColumn.title] ? JSON.parse(record.row[coverImageColumn.title]) : []
if (coverImageColumn?.title && record.row[coverImageColumn.title]) {
return typeof record.row[coverImageColumn.title] === 'string'
? JSON.parse(record.row[coverImageColumn.title])
: record.row[coverImageColumn.title]
}
return []
} catch (e) {
return []
}

33
packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { UITypes } from 'nocodb-sdk'
import { AllowedColumnTypesForQrCode, UITypes } from 'nocodb-sdk'
import type { SelectProps } from 'ant-design-vue'
import { useVModel } from '#imports'
@ -21,18 +21,14 @@ const vModel = useVModel(props, 'modelValue', emit)
const { setAdditionalValidations, validateInfos, column } = useColumnCreateStoreOrThrow()
const allowedColumnTypesForQrValue = [
UITypes.Formula,
UITypes.SingleLineText,
UITypes.LongText,
UITypes.PhoneNumber,
UITypes.URL,
UITypes.Email,
] as string[]
const columnsAllowedAsQrValue = computed<SelectProps['options']>(() => {
return fields.value
?.filter((el) => el.fk_column_id && allowedColumnTypesForQrValue.includes(metaColumnById.value[el.fk_column_id].uidt))
?.filter(
(el) =>
el.fk_column_id &&
// AllowedColumnTypesForQrCode.map((el) => el.toString()).includes(metaColumnById.value[el.fk_column_id].uidt),
AllowedColumnTypesForQrCode.includes(metaColumnById.value[el.fk_column_id].uidt as UITypes),
)
.map((field) => {
return {
value: field.fk_column_id,
@ -54,17 +50,10 @@ setAdditionalValidations({
<template>
<a-row>
<a-col :span="24">
<a-form-item
class="flex w-1/2 pb-2 nc-qr-code-value-column-select"
:label="$t('labels.qrCodeValueColumn')"
v-bind="validateInfos.fk_qr_value_column_id"
>
<a-select
v-model:value="vModel.fk_qr_value_column_id"
:options="columnsAllowedAsQrValue"
placeholder="Select a column for the QR code value"
@click.stop
/>
<a-form-item class="flex w-1/2 pb-2 nc-qr-code-value-column-select" :label="$t('labels.qrCodeValueColumn')"
v-bind="validateInfos.fk_qr_value_column_id">
<a-select v-model:value="vModel.fk_qr_value_column_id" :options="columnsAllowedAsQrValue"
placeholder="Select a column for the QR code value" @click.stop />
</a-form-item>
</a-col>
</a-row>

15
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -122,11 +122,13 @@ if (isKanban.value) {
}
}
const cellWrapperEl = (wrapperEl: HTMLElement) => {
nextTick(() => {
;(wrapperEl?.querySelector('input,select,textarea') as HTMLInputElement)?.focus()
const cellWrapperEl = ref<HTMLElement>()
onMounted(() => {
setTimeout(() => {
;(cellWrapperEl.value?.querySelector('input,select,textarea') as HTMLInputElement)?.focus()
})
}
})
</script>
<script lang="ts">
@ -163,7 +165,10 @@ export default {
<LazySmartsheetHeaderCell v-else :column="col" />
<div :ref="i ? null : cellWrapperEl" class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2">
<div
:ref="i ? null : (el) => (cellWrapperEl = el)"
class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2"
>
<LazySmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :row="row" :column="col" />
<LazySmartsheetCell

1
packages/nc-gui/components/smartsheet/header/CellIcon.ts

@ -19,7 +19,6 @@ import {
isJSON,
isPercent,
isPhoneNumber,
isPrimary,
isRating,
isSet,
isSingleSelect,

27
packages/nc-gui/components/smartsheet/toolbar/SearchData.vue

@ -1,9 +1,22 @@
<script lang="ts" setup>
import { ReloadViewDataHookInj, computed, inject, onClickOutside, ref, useSmartsheetStoreOrThrow } from '#imports'
import {
ActiveViewInj,
ReloadViewDataHookInj,
computed,
inject,
onClickOutside,
ref,
useFieldQuery,
useSmartsheetStoreOrThrow,
} from '#imports'
const reloadData = inject(ReloadViewDataHookInj)!
const { search, meta } = useSmartsheetStoreOrThrow()
const { meta } = useSmartsheetStoreOrThrow()
const activeView = inject(ActiveViewInj, ref())
const { search, loadFieldQuery } = useFieldQuery(activeView)
const isDropdownOpen = ref(false)
@ -18,6 +31,16 @@ const columns = computed(() =>
})),
)
watch(
() => activeView.value?.id,
(n, o) => {
if (n !== o) {
loadFieldQuery(activeView)
}
},
{ immediate: true },
)
function onPressEnter() {
reloadData.trigger()
}

21
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -27,14 +27,6 @@ const meta = inject(MetaInj, ref())
const cellValue = inject(CellValueInj, ref())
const arrValue = computed(() => {
if (!cellValue.value) return []
if (Array.isArray(cellValue.value)) return cellValue.value
return [cellValue.value]
})
const relationColumn = computed(
() =>
meta.value?.columns?.find((c) => c.id === (column.value?.colOptions as LookupType)?.fk_relation_column_id) as
@ -66,6 +58,19 @@ const lookupColumn = computed(
| undefined,
)
const arrValue = computed(() => {
if (!cellValue.value) return []
// if lookup column is Attachment and relation type is Belongs to wrap the value in an array
// since the attachment component expects an array or JSON string array
if (lookupColumn.value?.uidt === UITypes.Attachment && relationColumn.value?.colOptions?.type === RelationTypes.BELONGS_TO)
return [cellValue.value]
if (Array.isArray(cellValue.value)) return cellValue.value
return [cellValue.value]
})
provide(MetaInj, lookupTableMeta)
provide(CellUrlDisableOverlayInj, ref(true))

34
packages/nc-gui/composables/useFieldQuery.ts

@ -0,0 +1,34 @@
import type { Ref } from 'vue'
import type { ViewType } from 'nocodb-sdk'
import { useState } from '#imports'
export function useFieldQuery(view: Ref<ViewType | undefined>) {
// initial search object
const emptyFieldQueryObj = {
field: '',
query: '',
}
// mapping view id (key) to corresponding emptyFieldQueryObj (value)
const searchMap = useState<Record<string, { field: string; query: string }>>('field-query-search-map', () => ({}))
// the fieldQueryObj under the current view
const search = useState<{ field: string; query: string }>('field-query-search', () => emptyFieldQueryObj)
// map current view id to emptyFieldQueryObj
if (view?.value?.id) {
searchMap.value[view!.value!.id] = search.value
}
// retrieve the fieldQueryObj of the given view id
// if it is not found in `searchMap`, init with emptyFieldQueryObj
const loadFieldQuery = (view: Ref<ViewType | undefined>) => {
if (!view.value?.id) return
if (!(view!.value!.id in searchMap.value)) {
searchMap.value[view!.value!.id!] = emptyFieldQueryObj
}
search.value = searchMap.value[view!.value!.id!]
}
return { search, loadFieldQuery }
}

19
packages/nc-gui/composables/useSmartsheetStore.ts

@ -1,7 +1,7 @@
import { ViewTypes } from 'nocodb-sdk'
import type { FilterType, KanbanType, SortType, TableType, ViewType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { computed, reactive, ref, unref, useInjectionState, useNuxtApp, useProject } from '#imports'
import { computed, ref, unref, useFieldQuery, useInjectionState, useNuxtApp, useProject } from '#imports'
const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
(
@ -17,12 +17,7 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
const cellRefs = ref<HTMLTableDataCellElement[]>([])
// state
// todo: move to grid view store
const search = reactive({
field: '',
query: '',
})
const { search } = useFieldQuery(view)
// getters
const isLocked = computed(() => view.value?.lock_type === 'locked')
@ -35,21 +30,20 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
const xWhere = computed(() => {
let where
const col =
(meta.value as TableType)?.columns?.find(({ id }) => id === search.field) ||
(meta.value as TableType)?.columns?.find(({ id }) => id === search.value.field) ||
(meta.value as TableType)?.columns?.find((v) => v.pv)
if (!col) return
if (!search.query.trim()) return
if (!search.value.query.trim()) return
if (['text', 'string'].includes(sqlUi.value.getAbstractType(col)) && col.dt !== 'bigint') {
where = `(${col.title},like,%${search.query.trim()}%)`
where = `(${col.title},like,%${search.value.query.trim()}%)`
} else {
where = `(${col.title},eq,${search.query.trim()})`
where = `(${col.title},eq,${search.value.query.trim()})`
}
return where
})
const isSqlView = computed(() => (meta.value as TableType)?.type === 'view')
const sorts = ref<SortType[]>(unref(initialSorts) ?? [])
const nestedFilters = ref<FilterType[]>(unref(initialFilters) ?? [])
@ -58,7 +52,6 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
meta,
isLocked,
$api,
search,
xWhere,
isPkAvail,
isForm,

74
packages/nc-gui/lang/vi.json

@ -16,7 +16,7 @@
"cancel": "Hủy bỏ",
"submit": "Nộp",
"create": "Tạo ra",
"duplicate": "Duplicate",
"duplicate": "Tạo bản sao",
"insert": "Chèn",
"delete": "Xóa bỏ",
"update": "Cập nhật",
@ -56,20 +56,20 @@
"notification": "Thông báo",
"reference": "Thẩm quyền giải quyết",
"function": "Chức năng",
"confirm": "Confirm",
"confirm": "Xác nhận",
"generate": "Generate",
"copy": "Copy",
"misc": "Miscellaneous",
"lock": "Lock",
"unlock": "Unlock",
"credentials": "Credentials",
"help": "Help",
"questions": "Questions",
"reachOut": "Reach out here",
"betaNote": "This feature is currently in beta.",
"copy": "Sao Chép",
"misc": "Các tùy chọn khác",
"lock": "Khoá",
"unlock": "Mở khoá",
"credentials": "Thông tin đăng nhập",
"help": "Trợ giúp",
"questions": "Câu hỏi",
"reachOut": "Tiếp cận sau",
"betaNote": "Tính năng này hiện không được hỗ trợ.",
"moreInfo": "More information can be found here",
"logs": "Logs",
"groupingField": "Grouping Field"
"logs": "Nhật ký",
"groupingField": "Nhóm theo trường"
},
"objects": {
"project": "Dự định",
@ -189,20 +189,20 @@
"headCreateProject": "Tạo dự án |. NOCODB.",
"headLogin": "Đăng nhập |. NOCODB.",
"resetPassword": "Đặt lại mật khẩu của bạn",
"teamAndSettings": "Team & Settings",
"apiDocs": "API Docs",
"importFromAirtable": "Import From Airtable",
"generateToken": "Generate Token",
"APIsAndSupport": "APIs & Support",
"teamAndSettings": "Cài đặt nhóm",
"apiDocs": "Văn bản API",
"importFromAirtable": "Nhập khẩu từ CSDL Airtable",
"generateToken": "Tạo mã Token",
"APIsAndSupport": "APIS và Hỗ trợ",
"helpCenter": "Help center",
"swaggerDocumentation": "Swagger Documentation",
"quickImportFrom": "Quick Import From",
"swaggerDocumentation": "Tài liệu Swagger",
"quickImportFrom": "Kết nhập nhanh dữ liệu từ",
"quickImport": "Quick Import",
"advancedSettings": "Advanced Settings",
"codeSnippet": "Code Snippet"
"advancedSettings": "Cài đặt Nâng cao",
"codeSnippet": "Thư viện mã"
},
"labels": {
"createdBy": "Created By",
"createdBy": "Đã tạo bởi",
"notifyVia": "Thông báo qua",
"projName": "Tên dự án",
"tableName": "Tên bảng.",
@ -220,7 +220,7 @@
"port": "Cổng số",
"username": "tên tài khoản",
"password": "Mật khẩu",
"schemaName": "Schema name",
"schemaName": "Tên lược đồ",
"database": "Cơ sở dữ liệu",
"action": "Hoạt động",
"actions": "Hành động",
@ -233,7 +233,7 @@
"where": "Ở đâu",
"cache": "Cache.",
"chat": "Trò chuyện",
"email": "E-mail",
"email": "Thư điện tử",
"storage": "Kho",
"uiAcl": "UI-ACL",
"models": "Mô hình",
@ -258,7 +258,7 @@
"bookDemo": "Đặt một bản demo miễn phí",
"getAnswered": "Nhận câu hỏi của bạn được trả lời",
"joinDiscord": "Tham gia thông minh",
"joinCommunity": "Join NocoDB Community",
"joinCommunity": "Tham gia cộng đồng",
"joinReddit": "Tham gia /r/NocoDB",
"followNocodb": "Theo dõi NocoDB"
},
@ -268,21 +268,21 @@
"childColumn": "Cột trẻ con.",
"onUpdate": "Trên bản cập nhật",
"onDelete": "Trên xóa",
"account": "Account",
"account": "Tài khoản",
"language": "Language",
"primaryColor": "Primary Color",
"accentColor": "Accent Color",
"customTheme": "Custom Theme",
"primaryColor": "Màu chính",
"accentColor": "Màu phụ",
"customTheme": "Giao diện tùy chỉnh",
"requestDataSource": "Request a data source you need?",
"apiKey": "API Key",
"sharedBase": "Shared Base",
"importData": "Import Data",
"importSecondaryViews": "Import Secondary Views",
"importData": "Kết nhập dữ liệu",
"importSecondaryViews": "Kết nhập Hiển thị thứ hai",
"importRollupColumns": "Import Rollup Columns",
"importLookupColumns": "Import Lookup Columns",
"importAttachmentColumns": "Import Attachment Columns",
"importFormulaColumns": "Import Formula Columns",
"noData": "No Data",
"importAttachmentColumns": "Kết nhập cột tệp đính kèm",
"importFormulaColumns": "Kết nhập cột công thức",
"noData": "Không có dữ liệu",
"goToDashboard": "Go to Dashboard",
"importing": "Importing",
"flattenNested": "Flatten Nested",
@ -426,13 +426,13 @@
"erd": {
"showColumns": "Show Columns",
"showPkAndFk": "Show Primary and Foreign Keys",
"showSqlViews": "Show SQL Views",
"showSqlViews": "Hiển thị truy vấn SQL",
"showMMTables": "Show Many to Many tables",
"showJunctionTableNames": "Show Junction Table Names"
},
"kanban": {
"collapseStack": "Collapse Stack",
"deleteStack": "Delete Stack",
"deleteStack": "Xóa ngăn xếp",
"stackedBy": "Stacked By",
"chooseGroupingField": "Choose a Grouping Field",
"addOrEditStack": "Add / Edit Stack"

2
packages/nc-gui/layouts/default.vue

@ -20,7 +20,7 @@ export default {
<template>
<div class="w-full h-full">
<Teleport :to="hasSidebar ? '#nc-sidebar-left' : null" :disabled="!hasSidebar">
<slot name="sidebar" />
<slot :key="$route.name" name="sidebar" />
</Teleport>
<a-layout-content>

4
packages/nc-gui/package-lock.json generated

@ -11738,7 +11738,7 @@
}
},
"node_modules/nocodb-sdk": {
"version": "0.99.0",
"version": "0.99.2",
"resolved": "file:../nocodb-sdk",
"license": "AGPL-3.0-or-later",
"dependencies": {
@ -26029,7 +26029,7 @@
}
},
"nocodb-sdk": {
"version": "0.99.0",
"version": "0.99.2",
"requires": {
"axios": "^0.21.1",
"jsep": "^1.3.6"

5
packages/nc-gui/pages/account/index/users.vue

@ -1,5 +0,0 @@
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-2">
<NuxtPage />
</div>
</template>

30
packages/nc-gui/pages/account/index/users/[[nestedPage]].vue

@ -5,18 +5,20 @@ const { isUIAllowed } = useUIPermission()
</script>
<template>
<template
v-if="
$route.params.nestedPage === 'password-reset' ||
(!isUIAllowed('superAdminUserManagement') && !isUIAllowed('superAdminAppSettings'))
"
>
<LazyAccountResetPassword />
</template>
<template v-else-if="$route.params.nestedPage === 'settings'">
<LazyAccountSignupSettings />
</template>
<template v-else-if="isUIAllowed('superAdminUserManagement')">
<LazyAccountUserList />
</template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-2">
<template
v-if="
$route.params.nestedPage === 'password-reset' ||
(!isUIAllowed('superAdminUserManagement') && !isUIAllowed('superAdminAppSettings'))
"
>
<LazyAccountResetPassword />
</template>
<template v-else-if="$route.params.nestedPage === 'settings'">
<LazyAccountSignupSettings />
</template>
<template v-else-if="isUIAllowed('superAdminUserManagement')">
<LazyAccountUserList />
</template>
</div>
</template>

156
packages/nc-gui/pages/index/index/index.vue

@ -212,92 +212,94 @@ const copyProjectMeta = async () => {
</a-dropdown>
</div>
<Transition name="layout" mode="out-in">
<div v-if="isLoading">
<a-skeleton />
</div>
<a-table
v-else
:custom-row="customRow"
:data-source="filteredProjects"
:pagination="{ position: ['bottomCenter'] }"
:table-layout="md ? 'auto' : 'fixed'"
>
<template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
<!--
TODO: bring back transition after fixing the bug with navigation
<Transition name="layout" mode="out-in"> -->
<div v-if="isLoading">
<a-skeleton />
</div>
<!-- Title -->
<a-table-column key="title" :title="$t('general.title')" data-index="title">
<template #default="{ text, record }">
<div class="flex items-center">
<div @click.stop>
<a-menu class="!border-0 !m-0 !p-0" trigger-sub-menu-action="click">
<template v-if="isUIAllowed('projectTheme')">
<a-sub-menu key="theme" popup-class-name="custom-color">
<template #title>
<div
class="color-selector"
:style="{
'background-color': getProjectPrimary(record),
'width': '8px',
'height': '100%',
}"
/>
</template>
<a-table
v-else
:custom-row="customRow"
:data-source="filteredProjects"
:pagination="{ position: ['bottomCenter'] }"
:table-layout="md ? 'auto' : 'fixed'"
>
<template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
<!-- Title -->
<a-table-column key="title" :title="$t('general.title')" data-index="title">
<template #default="{ text, record }">
<div class="flex items-center">
<div @click.stop>
<a-menu class="!border-0 !m-0 !p-0" trigger-sub-menu-action="click">
<template v-if="isUIAllowed('projectTheme')">
<a-sub-menu key="theme" popup-class-name="custom-color">
<template #title>
<div
class="color-selector"
:style="{
'background-color': getProjectPrimary(record),
'width': '8px',
'height': '100%',
}"
/>
</template>
<template #expandIcon></template>
<template #expandIcon></template>
<LazyGeneralColorPicker
:model-value="getProjectPrimary(record)"
:colors="projectThemeColors"
:row-size="9"
:advanced="false"
@input="handleProjectColor(record.id, $event)"
/>
<LazyGeneralColorPicker
:model-value="getProjectPrimary(record)"
:colors="projectThemeColors"
:row-size="9"
:advanced="false"
@input="handleProjectColor(record.id, $event)"
/>
<a-sub-menu key="pick-primary">
<template #title>
<div class="nc-project-menu-item group !py-0">
<ClarityColorPickerSolid class="group-hover:text-accent" />
Custom Color
</div>
</template>
<a-sub-menu key="pick-primary">
<template #title>
<div class="nc-project-menu-item group !py-0">
<ClarityColorPickerSolid class="group-hover:text-accent" />
Custom Color
</div>
</template>
<template #expandIcon></template>
<template #expandIcon></template>
<LazyGeneralChromeWrapper @input="handleProjectColor(record.id, $event)" />
</a-sub-menu>
<LazyGeneralChromeWrapper @input="handleProjectColor(record.id, $event)" />
</a-sub-menu>
</template>
</a-menu>
</div>
<div
class="capitalize color-transition group-hover:text-primary !w-[400px] h-full overflow-hidden overflow-ellipsis whitespace-nowrap pl-2"
>
{{ text }}
</div>
</a-sub-menu>
</template>
</a-menu>
</div>
</template>
</a-table-column>
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div class="flex items-center gap-2">
<MdiEditOutline v-e="['c:project:edit:rename']" class="nc-action-btn" @click.stop="navigateTo(`/${text}`)" />
<MdiDeleteOutline
class="nc-action-btn"
:data-testid="`delete-project-${record.title}`"
@click.stop="deleteProject(record)"
/>
<div
class="capitalize color-transition group-hover:text-primary !w-[400px] h-full overflow-hidden overflow-ellipsis whitespace-nowrap pl-2"
>
{{ text }}
</div>
</template>
</a-table-column>
</a-table>
</Transition>
</div>
</template>
</a-table-column>
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div class="flex items-center gap-2">
<MdiEditOutline v-e="['c:project:edit:rename']" class="nc-action-btn" @click.stop="navigateTo(`/${text}`)" />
<MdiDeleteOutline
class="nc-action-btn"
:data-testid="`delete-project-${record.title}`"
@click.stop="deleteProject(record)"
/>
</div>
</template>
</a-table-column>
</a-table>
<!-- </Transition> -->
</div>
</template>

2
packages/nc-lib-gui/package.json

@ -1,6 +1,6 @@
{
"name": "nc-lib-gui",
"version": "0.99.0",
"version": "0.99.2",
"description": "NocoDB GUI",
"author": {
"name": "NocoDB",

12
packages/nc-plugin/package-lock.json generated

@ -8353,9 +8353,9 @@
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
@ -18648,9 +18648,9 @@
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"

224
packages/noco-docs/content/en/engineering/playwright.md

@ -0,0 +1,224 @@
---
title: "Playwright E2E Testing"
description: "Overview to playwright based e2e tests"
position: 3260
category: "Engineering"
menuTitle: "Playwright E2E Testing"
---
## How to run tests
All the tests reside in `tests/playwright` folder.
Make sure to install the dependencies(in the playwright folder):
```bash
npm install
npx playwright install chromium --with-deps
```
### Run Test Server
Start the backend test server (in `packages/nocodb` folder):
```bash
npm run watch:run:playwright
```
Start the frontend test server (in `packages/nc-gui` folder):
```bash
NUXT_PAGE_TRANSITION_DISABLE=true npm run dev
```
### Running all tests
For selecting db type, rename `.env.example` to `.env` and set `E2E_DEV_DB_TYPE` to `sqlite`(default), `mysql` or `pg`.
headless mode(without opening browser):
```bash
npm run test
```
with browser:
```bash
npm run test:debug
```
</br>
</br>
For setting up mysql(sakila):
```bash
docker-compose -f ./tests/playwright/scripts/docker-compose-mysql-playwright.yml up -d
```
For setting up postgres(sakila):
```bash
docker-compose -f ./tests/playwright/scripts/docker-compose-playwright-pg.yml
```
### Running individual tests
Add `.only` to the test you want to run:
```js
test.only('should login', async ({ page }) => {
// ...
})
```
```bash
npm run test
```
## Concepts
### Independent tests
- All tests are independent of each other.
- Each test starts with a fresh project with a fresh sakila database(option to not use sakila db is also there).
- Each test creates a new user(email as `user@nocodb.com`) and logs in with that user to the dashboard.
Caveats:
- Some stuffs are shared i.e, users, plugins etc. So be catious while writing tests touching that. A fix for this is in the works.
- In test, we prefix email and project with the test id, which will be deleted after the test is done.
### What to test
- UI verification. This includes verifying the state of the UI element, i.e if the element is visible, if the element has a particular text etc.
- Test should verify all user flow. A test has a default timeout of 60 seconds. If a test is taking more than 60 seconds, it is a sign that the test should be broken down into smaller tests.
- Test should also verify all the side effects the feature(i.e. On adding a new column type, should verify column deletion as well) will have, and also error cases.
- Test name should be descriptive. It should be easy to understand what the test is doing by just reading the test name.
### Playwright
- Playwright is a nodejs library for automating chromium, firefox and webkit.
- For each test, a new browser context is created. This means that each test runs in a new incognito window.
- For assertion always use `expect` from `@playwright/test` library. This library provides a lot of useful assertions, which also has retry logic built in.
## Page Objects
- Page objects are used to abstract over the components/page. This makes the tests more readable and maintainable.
- All page objects are in `tests/playwright/pages` folder.
- All the test related code should be in page objects.
- Methods should be as thin as possible and its better to have multiple methods than one big method, which improves reusability.
The methods of a page object can be classified into 2 categories:
- Actions: Performs an UI actions like click, type, select etc. Is also responsible for waiting for the element to be ready and the action to be performed. This included waiting for API calls to complete.
- Assertions: Asserts the state of the UI element, i.e if the element is visible, if the element has a particular text etc. Use `expect` from `@playwright/test` and if not use `expect.poll` to wait for the assertion to pass.
## Writing a test
Let's write a test for testing filter functionality.
For simplicity, we will have `DashboardPage` implemented, which will have all the methods related to dashboard page and also its child components like Grid, etc.
### Create a test suite
Create a new file `filter.spec.ts` in `tests/playwright/tests` folder and use `setup` method to create a new project and user.
```js
import { test, expect } from '@playwright/test';
import setup, { NcContext } from '../setup';
test.describe('Filter', () => {
let context: NcContext;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
})
test('should filter', async ({ page }) => {
// ...
});
});
```
### Create a page object
Since filter is UI wise scoped to a `Toolbar` , we will add filter page object to `ToolbarPage` page object.
```js
export class ToolbarPage extends BasePage {
readonly parent: GridPage | GalleryPage | FormPage | KanbanPage;
readonly filter: ToolbarFilterPage;
constructor(parent: GridPage | GalleryPage | FormPage | KanbanPage) {
super(parent.rootPage);
this.parent = parent;
this.filter = new ToolbarFilterPage(this);
}
}
```
We will create `ToolbarFilterPage` page object, which will have all the methods related to filter.
```js
export class ToolbarFilterPage extends BasePage {
readonly toolbar: ToolbarPage;
constructor(toolbar: ToolbarPage) {
super(toolbar.rootPage);
this.toolbar = toolbar;
}
}
```
Here `BasePage` is an abstract class, which used to enforce structure for all page objects. Thus all page object *should* inherit `BasePage`.
- Helper methods like `waitForResponse` and `getClipboardText` (this can be access on any page object, with `this.waitForResponse`)
- Provides structure for page objects, enforces all Page objects to have `rootPage` property, which is the page object created in the test setup.
- Enforces all pages to have a `get` method which will return the locator of the main container of that page, hence we can have focused dom selection, i.e.
```js
// This will only select the button inside the container of the concerned page
await this.get().querySelector('button').count();
```
### Writing an action method
This a method which will reset/clear all the filters. Since this is an action method, it will also wait for the `delete` filter API to return. Ignoring this API call will cause flakiness in the test, down the line.
```js
async resetFilter() {
await this.waitForResponse({
uiAction: this.get().locator('.nc-filter-item-remove-btn').click(),
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: '/api/v1/db/meta/filters/',
});
}
```
### Writing an assertion/verification method
Here we use `expect` from `@playwright/test` library, which has retry logic built in.
```js
import { expect } from '@playwright/test';
async verifyFilter({ title }: { title: string }) {
await expect(
this.get().locator(`[data-testid="nc-fields-menu-${title}"]`).locator('input[type="checkbox"]')
).toBeChecked();
}
```
## Tips to avoid flakiness
- If an UI action, causes an API call or the UI state change, then wait for that API call to complete or the UI state to change.
- What to wait out can be situation specific, but in general, is best to wait for the final state to be reached, i.e. in the case of creating filter, while it seems like waiting for the filter API to complete is enough, but after its return the table rows are reloaded and the UI state changes, so its better to wait for the table rows to be reloaded.
## Accessing playwright report in the CI
- Open `Summary` tab in the CI workflow in github actions.
- Scroll down to `Artifacts` section.
- Access reports which suffixed with the db type and shard number(corresponding to the CI workerflow name). i.e `playwright-report-mysql-2` is for `playwright-mysql-2` workflow.
- Download it and run `npm install -D @playwright/test && npx playwright show-report ./` inside the downloaded folder.

3
packages/noco-docs/content/en/engineering/translation.md

@ -53,12 +53,11 @@ Refer following articles to get additional details about Crowdin Portal usage
#### GitHub changes
- Update enumeration in `enums.ts` [packages/nc-gui/lib/enums.ts]
- Map JSON path in `a.i18n.ts` [packages/nc-gui/plugins/a.i18n.ts]
- Update array in `6d_language_validation.js` [scripts/cypress/integration/common/6d_language_validation.js]
#### Crowdin changes [admin only]
- Open `NocoDB` project
- Click on `Language` on the home tab
- Select target language, `Update`
- Update array in `tests/playwright/tests/language.spec.ts`
![Screenshot 2022-09-08 at 10 52 59 PM](https://user-images.githubusercontent.com/86527202/189186570-5c1c7cad-6d3f-4937-ab4d-fa7ebe022cb1.png)

161
packages/noco-docs/content/en/engineering/testing.md → packages/noco-docs/content/en/engineering/unit-testing.md

@ -1,9 +1,9 @@
---
title: "Writing Tests"
description: "Overview to testing"
title: "Writing Unit Tests"
description: "Overview to Unit Testing"
position: 3250
category: "Engineering"
menuTitle: "Writing Tests"
menuTitle: "Unit Testing"
---
## Unit Tests
@ -189,158 +189,3 @@ function tableTest() {
});
}
```
## Cypress Tests
### End-to-end (E2E) Tests
Cypress tests are divided into 4 suites
- SQLite tests
- Postgres tests
- MySQL tests
- Quick import tests
First 3 suites, each have 4 test category
- Table operations (create, delete, rename, add column, delete column, rename column)
- Views (Grid, Gallery, Form)
- Roles (user profiles, access control & preview)
- Miscellaneous (Import, i18n, etc)
### SQLite Tests (XCDB Project)
```shell
# install dependencies(cypress)
npm install
# start MySQL database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# Run backend api using following command
npm run start:xcdb-api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run one of 4 test scripts
# - Table operations : xcdb-restTableOps.js
# - Views : xcdb-restViews.js
# - Roles & access control : xcdb-restRoles.js
# - Miscellaneous : xcdb-restMisc.js
```
### MySQL Tests (External DB Project)
```shell
# install dependencies(cypress)
npm install
# start MySQL database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# Run backend api using following command
npm run start:api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run one of 4 test scripts
# - Table operations : restTableOps.js
# - Views : restViews.js
# - Roles & access control : restRoles.js
# - Miscellaneous : restMisc.js
```
### Postgres Tests (External DB Project)
```shell
# install dependencies(cypress)
npm install
# start Postgres database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d
# Run backend api using following command
npm run start:api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run one of 4 test scripts
# - Table operations : pg-restTableOps.js
# - Views : pg-restViews.js
# - Roles & access control : pg-restRoles.js
# - Miscellaneous : pg-restMisc.js
```
### Quick Import Tests (SQLite Project)
```shell
# install dependencies(cypress)
npm install
# start MySQL database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-cypress.yml up
# copy existing xcdb (v0.91.7) database to ./packages/nocodb/
cp ./scripts/cypress/fixtures/quickTest/noco_0_91_7.db ./packages/nocodb/noco.db
# Run backend api using following command
npm run start:api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run test script
# - quickTest.js
```
### Quick import tests (Postgres)
```shell
# install dependencies(cypress)
npm install
# start PG database using docker compose
docker-compose -f ./scripts/cypress/docker-compose-pg.yml up -d
# copy existing xcdb (v0.91.7) database to ./packages/nocodb/
cp ./scripts/cypress/fixtures/quickTest/noco_0_91_7.db ./packages/nocodb/noco.db
# Run backend api using following command
npm run start:api:cache
# Run frontend web UI using following command
npm run start:web
# wait until both 3000 and 8080 ports are available
# or run following command to run it with GUI
npm run cypress:open
# run test script
# - quickTest.js
```
## Accessing CI-CD CY Screenshots
1. On Jobs link, click on `Summary`
![Screenshot 2022-10-31 at 9 25 23 PM](https://user-images.githubusercontent.com/86527202/199052696-af0bf066-d82f-487a-b487-602f55594fd7.png)
2. Click on `Artifacts`
![Screenshot 2022-10-31 at 9 26 01 PM](https://user-images.githubusercontent.com/86527202/199052712-04508921-32b1-4926-8291-396c804f7c3b.png)
3. Download logs for desired suite
![Screenshot 2022-10-31 at 9 26 34 PM](https://user-images.githubusercontent.com/86527202/199052727-9aebbdd1-749e-4bda-ab00-3cdd0e3f48fe.png)

12
packages/noco-docs/package-lock.json generated

@ -10047,9 +10047,9 @@
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -24670,9 +24670,9 @@
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}

4
packages/nocodb-sdk/package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "nocodb-sdk",
"version": "0.99.0",
"version": "0.99.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "nocodb-sdk",
"version": "0.99.0",
"version": "0.99.2",
"license": "AGPL-3.0-or-later",
"dependencies": {
"axios": "^0.21.1",

2
packages/nocodb-sdk/package.json

@ -1,6 +1,6 @@
{
"name": "nocodb-sdk",
"version": "0.99.0",
"version": "0.99.2",
"description": "NocoDB SDK",
"main": "build/main/index.js",
"typings": "build/main/index.d.ts",

1
packages/nocodb-sdk/src/index.ts

@ -1,6 +1,7 @@
export * from './lib/XcUIBuilder';
export * from './lib/XcNotification';
export * from './lib/Api';
export * from './lib/columnRules';
export * from './lib/sqlUi';
export * from './lib/globals';
export * from './lib/helperFunctions';

10
packages/nocodb-sdk/src/lib/columnRules/QrCodeRules.ts

@ -0,0 +1,10 @@
import UITypes from '../UITypes';
export const AllowedColumnTypesForQrCode = [
UITypes.Formula,
UITypes.SingleLineText,
UITypes.LongText,
UITypes.PhoneNumber,
UITypes.URL,
UITypes.Email,
];

1
packages/nocodb-sdk/src/lib/columnRules/index.ts

@ -0,0 +1 @@
export * from './QrCodeRules';

15
packages/nocodb/docker-compose.yml

@ -447,18 +447,3 @@ services:
cp /home/app/tests/sqlite-dump/sakila.db /home/sakila.db
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"#
cd /home/app/ && npm i && npm run test:graphql
xc-cypress-test:
image: node:12.22.1-slim
ports:
- 8080:8080
volumes:
- ./:/home/app
command:
- /bin/bash
- -c
- |
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
cd /home/app/ && npm i && npm run run

359
packages/nocodb/package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "nocodb",
"version": "0.99.0",
"version": "0.99.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "nocodb",
"version": "0.99.0",
"version": "0.99.2",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@google-cloud/storage": "^5.7.2",
@ -64,7 +64,7 @@
"mysql2": "^2.2.5",
"nanoid": "^3.1.20",
"nc-help": "0.2.79",
"nc-lib-gui": "0.99.0",
"nc-lib-gui": "0.99.2",
"nc-plugin": "0.1.2",
"ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk",
@ -119,7 +119,7 @@
"eslint-plugin-functional": "^3.0.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^4.0.0",
"mocha": "^8.1.1",
"mocha": "^10.1.0",
"nodemon": "^2.0.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
@ -1607,12 +1607,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@ungap/promise-all-settled": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
"dev": true
},
"node_modules/@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -7347,15 +7341,6 @@
"graphql": ">=0.8.0"
}
},
"node_modules/growl": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true,
"engines": {
"node": ">=4.x"
}
},
"node_modules/gtoken": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz",
@ -8451,6 +8436,18 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@ -9278,15 +9275,19 @@
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="
},
"node_modules/log-symbols": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
"integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
"dev": true,
"dependencies": {
"chalk": "^4.0.0"
"chalk": "^4.1.0",
"is-unicode-supported": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-symbols/node_modules/chalk": {
@ -9935,43 +9936,39 @@
}
},
"node_modules/mocha": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz",
"integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz",
"integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==",
"dev": true,
"dependencies": {
"@ungap/promise-all-settled": "1.1.2",
"ansi-colors": "4.1.1",
"browser-stdout": "1.3.1",
"chokidar": "3.5.1",
"debug": "4.3.1",
"chokidar": "3.5.3",
"debug": "4.3.4",
"diff": "5.0.0",
"escape-string-regexp": "4.0.0",
"find-up": "5.0.0",
"glob": "7.1.6",
"growl": "1.10.5",
"glob": "7.2.0",
"he": "1.2.0",
"js-yaml": "4.0.0",
"log-symbols": "4.0.0",
"minimatch": "3.0.4",
"js-yaml": "4.1.0",
"log-symbols": "4.1.0",
"minimatch": "5.0.1",
"ms": "2.1.3",
"nanoid": "3.1.20",
"serialize-javascript": "5.0.1",
"nanoid": "3.3.3",
"serialize-javascript": "6.0.0",
"strip-json-comments": "3.1.1",
"supports-color": "8.1.1",
"which": "2.0.2",
"wide-align": "1.1.3",
"workerpool": "6.1.0",
"workerpool": "6.2.1",
"yargs": "16.2.0",
"yargs-parser": "20.2.4",
"yargs-unparser": "2.0.0"
},
"bin": {
"_mocha": "bin/_mocha",
"mocha": "bin/mocha"
"mocha": "bin/mocha.js"
},
"engines": {
"node": ">= 10.12.0"
"node": ">= 14.0.0"
},
"funding": {
"type": "opencollective",
@ -9993,50 +9990,6 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/mocha/node_modules/chokidar": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
"dev": true,
"dependencies": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.5.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.1"
}
},
"node_modules/mocha/node_modules/debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/mocha/node_modules/debug/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/mocha/node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -10066,9 +10019,9 @@
}
},
"node_modules/mocha/node_modules/glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
@ -10085,10 +10038,22 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/mocha/node_modules/glob/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/mocha/node_modules/js-yaml": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz",
"integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@ -10113,21 +10078,30 @@
}
},
"node_modules/mocha/node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
"brace-expansion": "^2.0.1"
},
"engines": {
"node": "*"
"node": ">=10"
}
},
"node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/mocha/node_modules/nanoid": {
"version": "3.1.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
"integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
"integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
@ -10151,16 +10125,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mocha/node_modules/readdirp": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
"node_modules/mocha/node_modules/serialize-javascript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
"integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
"dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
"randombytes": "^2.1.0"
}
},
"node_modules/mocha/node_modules/supports-color": {
@ -10610,9 +10581,9 @@
}
},
"node_modules/nc-lib-gui": {
"version": "0.99.0",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.99.0.tgz",
"integrity": "sha512-3wSn6FVTffbPHpV0OWJeaHJ4q9/1y3QM5ltVNf5sIjunN9HEBstvf+EjJ5UGQIad6/CYKGR5DnIi0uUux/ymOA==",
"version": "0.99.2",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.99.2.tgz",
"integrity": "sha512-ghjsyPGHGMMx4zvHqX5mhJhW/DZuZd1pYh8+zm9eru6v/xx6QUQAfcpZrMCfy9MadIY8haiva1khGAZMnY6grQ==",
"dependencies": {
"express": "^4.17.1"
}
@ -10663,7 +10634,7 @@
"dev": true
},
"node_modules/nocodb-sdk": {
"version": "0.99.0",
"version": "0.99.2",
"resolved": "file:../nocodb-sdk",
"license": "AGPL-3.0-or-later",
"dependencies": {
@ -17441,9 +17412,9 @@
}
},
"node_modules/workerpool": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz",
"integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
"integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
"dev": true
},
"node_modules/wrap-ansi": {
@ -18937,12 +18908,6 @@
"eslint-visitor-keys": "^2.0.0"
}
},
"@ungap/promise-all-settled": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
"dev": true
},
"@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -23525,12 +23490,6 @@
"integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==",
"requires": {}
},
"growl": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true
},
"gtoken": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz",
@ -24308,6 +24267,12 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true
},
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@ -24972,12 +24937,13 @@
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="
},
"log-symbols": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
"integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
"dev": true,
"requires": {
"chalk": "^4.0.0"
"chalk": "^4.1.0",
"is-unicode-supported": "^0.1.0"
},
"dependencies": {
"chalk": {
@ -25517,33 +25483,29 @@
}
},
"mocha": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz",
"integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz",
"integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==",
"dev": true,
"requires": {
"@ungap/promise-all-settled": "1.1.2",
"ansi-colors": "4.1.1",
"browser-stdout": "1.3.1",
"chokidar": "3.5.1",
"debug": "4.3.1",
"chokidar": "3.5.3",
"debug": "4.3.4",
"diff": "5.0.0",
"escape-string-regexp": "4.0.0",
"find-up": "5.0.0",
"glob": "7.1.6",
"growl": "1.10.5",
"glob": "7.2.0",
"he": "1.2.0",
"js-yaml": "4.0.0",
"log-symbols": "4.0.0",
"minimatch": "3.0.4",
"js-yaml": "4.1.0",
"log-symbols": "4.1.0",
"minimatch": "5.0.1",
"ms": "2.1.3",
"nanoid": "3.1.20",
"serialize-javascript": "5.0.1",
"nanoid": "3.3.3",
"serialize-javascript": "6.0.0",
"strip-json-comments": "3.1.1",
"supports-color": "8.1.1",
"which": "2.0.2",
"wide-align": "1.1.3",
"workerpool": "6.1.0",
"workerpool": "6.2.1",
"yargs": "16.2.0",
"yargs-parser": "20.2.4",
"yargs-unparser": "2.0.0"
@ -25561,39 +25523,6 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"chokidar": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
"dev": true,
"requires": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.5.0"
}
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@ -25611,9 +25540,9 @@
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@ -25622,12 +25551,23 @@
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"dependencies": {
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
}
}
},
"js-yaml": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz",
"integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
@ -25643,18 +25583,29 @@
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
"brace-expansion": "^2.0.1"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
}
}
},
"nanoid": {
"version": "3.1.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
"integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
"integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
"dev": true
},
"p-locate": {
@ -25666,13 +25617,13 @@
"p-limit": "^3.0.2"
}
},
"readdirp": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
"serialize-javascript": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
"integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
"randombytes": "^2.1.0"
}
},
"supports-color": {
@ -26047,9 +25998,9 @@
}
},
"nc-lib-gui": {
"version": "0.99.0",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.99.0.tgz",
"integrity": "sha512-3wSn6FVTffbPHpV0OWJeaHJ4q9/1y3QM5ltVNf5sIjunN9HEBstvf+EjJ5UGQIad6/CYKGR5DnIi0uUux/ymOA==",
"version": "0.99.2",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.99.2.tgz",
"integrity": "sha512-ghjsyPGHGMMx4zvHqX5mhJhW/DZuZd1pYh8+zm9eru6v/xx6QUQAfcpZrMCfy9MadIY8haiva1khGAZMnY6grQ==",
"requires": {
"express": "^4.17.1"
}
@ -26091,7 +26042,7 @@
"dev": true
},
"nocodb-sdk": {
"version": "0.99.0",
"version": "0.99.2",
"requires": {
"axios": "^0.21.1",
"jsep": "^1.3.6"
@ -31405,9 +31356,9 @@
}
},
"workerpool": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz",
"integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
"integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
"dev": true
},
"wrap-ansi": {

13
packages/nocodb/package.json

@ -1,6 +1,6 @@
{
"name": "nocodb",
"version": "0.99.0",
"version": "0.99.2",
"description": "NocoDB Backend",
"main": "dist/bundle.js",
"author": {
@ -37,11 +37,8 @@
"watch:build": "nodemon -e ts,js -w ./src -x npm run build",
"watch:run": "cross-env NC_DISABLE_TELE1=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"",
"watch:run:playwright": "rm -f ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db PLAYWRIGHT_TEST=true NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/testDocker --log-error --project tsconfig.json\"",
"watch:run:playwright:quick": "rm -f ./test_noco.db; cp ../../scripts/cypress/fixtures/quickTest/noco_0_91_7.db ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"",
"watch:run:playwright:pg:cyquick": "rm -f ./test_noco.db; cp ../../scripts/cypress/fixtures/quickTest/noco_0_91_7.db ./test_noco.db; cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"",
"watch:run:cypress": "cross-env EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"",
"watch:run:cypress:pg": "cross-env EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG --log-error --project tsconfig.json\"",
"watch:run:cypress:pg:cyquick": "cross-env EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"",
"watch:run:playwright:quick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env DATABASE_URL=sqlite:./test_noco.db NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/docker --log-error --project tsconfig.json\"",
"watch:run:playwright:pg:cyquick": "rm -f ./test_noco.db; cp ../../tests/playwright/fixtures/noco_0_91_7.db ./test_noco.db; cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG_CyQuick.ts --log-error --project tsconfig.json\"",
"watch:run:mysql": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunMysql --log-error --project tsconfig.json\"",
"watch:run:pg": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/run/dockerRunPG --log-error --project tsconfig.json\"",
"run": "ts-node src/run/docker",
@ -107,7 +104,7 @@
"mysql2": "^2.2.5",
"nanoid": "^3.1.20",
"nc-help": "0.2.79",
"nc-lib-gui": "0.99.0",
"nc-lib-gui": "0.99.2",
"nc-plugin": "0.1.2",
"ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk",
@ -162,7 +159,7 @@
"eslint-plugin-functional": "^3.0.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^4.0.0",
"mocha": "^8.1.1",
"mocha": "^10.1.0",
"nodemon": "^2.0.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",

120
packages/nocodb/src/lib/db/sql-client/lib/mssql/MssqlClient.ts

@ -356,12 +356,14 @@ class MssqlClient extends KnexClient {
try {
/** ************** START : create _evolution table if not exists *************** */
const exists = await this.sqlClient.schema.withSchema(this.schema).hasTable(args.tn);
const exists = await this.sqlClient.schema
.withSchema(this.schema)
.hasTable(args.tn);
if (!exists) {
await this.sqlClient.schema.withSchema(this.schema).createTable(
args.tn,
function (table) {
await this.sqlClient.schema
.withSchema(this.schema)
.createTable(args.tn, function (table) {
table.increments();
table.string('title').notNullable();
table.string('titleDown').nullable();
@ -371,8 +373,7 @@ class MssqlClient extends KnexClient {
table.integer('status').nullable();
table.dateTime('created');
table.timestamps();
}
);
});
log.debug('Table created:', `${this.getTnPath(args.tn)}`);
} else {
log.debug(`${this.getTnPath(args.tn)} tables exists`);
@ -394,7 +395,9 @@ class MssqlClient extends KnexClient {
log.api(`${_func}:args:`, args);
try {
result.data.value = await this.sqlClient.schema.withSchema(this.schema).hasTable(args.tn);
result.data.value = await this.sqlClient.schema
.withSchema(this.schema)
.hasTable(args.tn);
} catch (e) {
log.ppe(e, _func);
throw e;
@ -1343,8 +1346,9 @@ class MssqlClient extends KnexClient {
log.api(`${func}:args:`, args);
// `DROP TRIGGER ${args.trigger_name}`
try {
const query = `${this.querySeparator()}DROP TRIGGER IF EXISTS ${args.trigger_name
}`;
const query = `${this.querySeparator()}DROP TRIGGER IF EXISTS ${
args.trigger_name
}`;
await this.sqlClient.raw(query);
@ -1474,7 +1478,8 @@ class MssqlClient extends KnexClient {
{
sql:
this.querySeparator() +
`DROP FUNCTION IF EXISTS ${args.function_name
`DROP FUNCTION IF EXISTS ${
args.function_name
};${this.querySeparator()}\n${args.create_function}`,
},
],
@ -1482,7 +1487,8 @@ class MssqlClient extends KnexClient {
{
sql:
this.querySeparator() +
`DROP FUNCTION IF EXISTS ${args.function_name
`DROP FUNCTION IF EXISTS ${
args.function_name
};${this.querySeparator()} ${args.oldCreateFunction}`,
},
],
@ -1554,7 +1560,8 @@ class MssqlClient extends KnexClient {
{
sql:
this.querySeparator() +
`DROP PROCEDURE IF EXISTS ${args.procedure_name
`DROP PROCEDURE IF EXISTS ${
args.procedure_name
};${this.querySeparator()}\n${args.create_procedure}`,
},
],
@ -1562,7 +1569,8 @@ class MssqlClient extends KnexClient {
{
sql:
this.querySeparator() +
`DROP PROCEDURE IF EXISTS ${args.procedure_name
`DROP PROCEDURE IF EXISTS ${
args.procedure_name
};${this.querySeparator()}${args.oldCreateProcedure}`,
},
],
@ -1627,7 +1635,8 @@ class MssqlClient extends KnexClient {
try {
// await this.sqlClient.raw(`DROP TRIGGER ${args.trigger_name}`);
await this.sqlClient.raw(
`ALTER TRIGGER ${args.trigger_name} ON ${this.getTnPath(args.tn)} \n${args.timing
`ALTER TRIGGER ${args.trigger_name} ON ${this.getTnPath(args.tn)} \n${
args.timing
} ${args.event}\n AS\n${args.statement}`
);
@ -1712,7 +1721,8 @@ class MssqlClient extends KnexClient {
{
sql:
this.querySeparator() +
`DROP VIEW ${args.view_name} ; ${this.querySeparator()}${args.oldViewDefination
`DROP VIEW ${args.view_name} ; ${this.querySeparator()}${
args.oldViewDefination
};`,
},
],
@ -1807,7 +1817,10 @@ class MssqlClient extends KnexClient {
const downStatement =
this.querySeparator() +
this.sqlClient.schema.withSchema(this.schema).dropTable(args.table).toString();
this.sqlClient.schema
.withSchema(this.schema)
.dropTable(args.table)
.toString();
this.emit(`Success : ${upQuery}`);
@ -1849,7 +1862,15 @@ class MssqlClient extends KnexClient {
BEGIN
SET NOCOUNT ON;
UPDATE ?? Set ?? = GetDate() WHERE ?? in (SELECT ?? FROM Inserted)
END;`, [triggerName, this.getTnPath(args.table_name), this.getTnPath(args.table_name), column.column_name, pk.column_name, pk.column_name]
END;`,
[
triggerName,
this.getTnPath(args.table_name),
this.getTnPath(args.table_name),
column.column_name,
pk.column_name,
pk.column_name,
]
);
upQuery += triggerCreateQuery;
@ -2051,7 +2072,10 @@ class MssqlClient extends KnexClient {
/** ************** create up & down statements *************** */
const upStatement =
this.querySeparator() +
this.sqlClient.schema.withSchema(this.schema).dropTable(args.tn).toString();
this.sqlClient.schema
.withSchema(this.schema)
.dropTable(args.tn)
.toString();
let downQuery = this.querySeparator() + this.createTable(args.tn, args);
this.emit(`Success : ${upStatement}`);
@ -2065,8 +2089,9 @@ class MssqlClient extends KnexClient {
for (const relation of relationsList) {
downQuery +=
this.querySeparator() +
(await this.sqlClient.withSchema(this.schema).schema
.table(relation.tn, (table) => {
(await this.sqlClient
.withSchema(this.schema)
.schema.table(relation.tn, (table) => {
table = table
.foreign(relation.cn, null)
.references(relation.rcn)
@ -2109,7 +2134,8 @@ class MssqlClient extends KnexClient {
)) {
downQuery +=
this.querySeparator() +
this.sqlClient.schema.withSchema(this.schema)
this.sqlClient.schema
.withSchema(this.schema)
.table(tn, function (table) {
if (non_unique) {
table.index(columns, key_name);
@ -2154,19 +2180,22 @@ class MssqlClient extends KnexClient {
try {
const self = this;
await this.sqlClient.schema.table(this.getTnPath(args.childTable), function (table) {
table = table
.foreign(args.childColumn, foreignKeyName)
.references(args.parentColumn)
.on(self.getTnPath(args.parentTable));
if (args.onUpdate) {
table = table.onUpdate(args.onUpdate);
}
if (args.onDelete) {
table = table.onDelete(args.onDelete);
await this.sqlClient.schema.table(
this.getTnPath(args.childTable),
function (table) {
table = table
.foreign(args.childColumn, foreignKeyName)
.references(args.parentColumn)
.on(self.getTnPath(args.parentTable));
if (args.onUpdate) {
table = table.onUpdate(args.onUpdate);
}
if (args.onDelete) {
table = table.onDelete(args.onDelete);
}
}
});
);
const upStatement =
this.querySeparator() +
@ -2227,9 +2256,12 @@ class MssqlClient extends KnexClient {
try {
const self = this;
await this.sqlClient.schema.table(this.getTnPath(args.childTable), function (table) {
table.dropForeign(args.childColumn, foreignKeyName);
});
await this.sqlClient.schema.table(
this.getTnPath(args.childTable),
function (table) {
table.dropForeign(args.childColumn, foreignKeyName);
}
);
const upStatement =
this.querySeparator() +
@ -2557,10 +2589,10 @@ class MssqlClient extends KnexClient {
query += n.ai ? ' IDENTITY(1,1)' : ' ';
query += defaultValue
? this.genQuery(
` CONSTRAINT ?? DEFAULT ${defaultValue}`,
[`DF_${t}_${n.cn}`],
shouldSanitize
)
` CONSTRAINT ?? DEFAULT ${defaultValue}`,
[`DF_${t}_${n.cn}`],
shouldSanitize
)
: '';
if (defaultValue) {
n.default_constraint_name = `DF_${t}_${n.cn}`;
@ -2572,10 +2604,10 @@ class MssqlClient extends KnexClient {
query += n.ai ? ' IDENTITY(1,1)' : ' ';
query += defaultValue
? this.genQuery(
` CONSTRAINT ?? DEFAULT ${defaultValue}`,
[`DF_${t}_${n.cn}`],
shouldSanitize
)
` CONSTRAINT ?? DEFAULT ${defaultValue}`,
[`DF_${t}_${n.cn}`],
shouldSanitize
)
: ' ';
// shouldSanitize = false;
query = this.genQuery(

65
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts

@ -100,9 +100,10 @@ class BaseModelSqlv2 {
qb.where(_wherePk(this.model.primaryKeys, id));
const data = (await this.extractRawQueryAndExec(qb))?.[0];
let data = (await this.extractRawQueryAndExec(qb))?.[0];
if (data) {
data = this.convertAttachmentType(data);
const proto = await this.getProto();
data.__proto__ = proto;
}
@ -158,9 +159,10 @@ class BaseModelSqlv2 {
qb.orderBy(this.model.primaryKey.column_name);
}
const data = await qb.first();
let data = await qb.first();
if (data) {
data = this.convertAttachmentType(data);
const proto = await this.getProto();
data.__proto__ = proto;
}
@ -252,7 +254,8 @@ class BaseModelSqlv2 {
if (!ignoreViewFilterAndSort) applyPaginate(qb, rest);
const proto = await this.getProto();
const data = await this.extractRawQueryAndExec(qb);
let data = await this.extractRawQueryAndExec(qb);
data = this.convertAttachmentType(data);
return data?.map((d) => {
d.__proto__ = proto;
@ -364,8 +367,7 @@ class BaseModelSqlv2 {
qb.groupBy(args.column_name);
if (sorts) await sortV2(sorts, qb, this.dbDriver);
applyPaginate(qb, rest);
const data = await qb;
return data;
return this.convertAttachmentType(await qb);
}
async multipleHmList({ colId, ids }, args: { limit?; offset? } = {}) {
@ -424,7 +426,8 @@ class BaseModelSqlv2 {
.as('list')
);
const children = await this.extractRawQueryAndExec(childQb);
let children = await this.extractRawQueryAndExec(childQb);
children = this.convertAttachmentType(children);
const proto = await (
await Model.getBaseModelSQL({
id: childTable.id,
@ -551,7 +554,8 @@ class BaseModelSqlv2 {
await childModel.selectObject({ qb });
const children = await this.extractRawQueryAndExec(qb);
let children = await this.extractRawQueryAndExec(qb);
children = this.convertAttachmentType(children);
const proto = await (
await Model.getBaseModelSQL({
@ -672,6 +676,7 @@ class BaseModelSqlv2 {
if (this.isMySQL) {
children = children[0];
}
children = this.convertAttachmentType(children);
const proto = await (
await Model.getBaseModelSQL({
id: rtnId,
@ -736,7 +741,8 @@ class BaseModelSqlv2 {
qb.limit(+rest?.limit || 25);
qb.offset(+rest?.offset || 0);
const children = await this.extractRawQueryAndExec(qb);
let children = await this.extractRawQueryAndExec(qb);
children = this.convertAttachmentType(children);
const proto = await (
await Model.getBaseModelSQL({ id: rtnId, dbDriver: this.dbDriver })
).getProto();
@ -962,8 +968,8 @@ class BaseModelSqlv2 {
applyPaginate(qb, rest);
const proto = await childModel.getProto();
const data = await qb;
let data = await qb;
data = this.convertAttachmentType(data);
return data.map((c) => {
c.__proto__ = proto;
return c;
@ -1077,7 +1083,8 @@ class BaseModelSqlv2 {
applyPaginate(qb, rest);
const proto = await childModel.getProto();
const data = await this.extractRawQueryAndExec(qb);
let data = await this.extractRawQueryAndExec(qb);
data = this.convertAttachmentType(data);
return data.map((c) => {
c.__proto__ = proto;
@ -1195,7 +1202,8 @@ class BaseModelSqlv2 {
applyPaginate(qb, rest);
const proto = await parentModel.getProto();
const data = await this.extractRawQueryAndExec(qb);
let data = await this.extractRawQueryAndExec(qb);
data = this.convertAttachmentType(data);
return data.map((c) => {
c.__proto__ = proto;
@ -2647,7 +2655,9 @@ class BaseModelSqlv2 {
const proto = await this.getProto();
const result = (await groupedQb)?.map((d) => {
let data = await groupedQb;
data = this.convertAttachmentType(data);
const result = data?.map((d) => {
d.__proto__ = proto;
return d;
});
@ -2764,6 +2774,35 @@ class BaseModelSqlv2 {
)
: await this.dbDriver.raw(query);
}
private _convertAttachmentType(attachmentColumns, d) {
attachmentColumns.forEach((col) => {
if (d[col.title] && typeof d[col.title] === 'string') {
d[col.title] = JSON.parse(d[col.title]);
}
});
return d;
}
private convertAttachmentType(data) {
// attachment is stored in text and parse in UI
// convertAttachmentType is used to convert the response in string to array of object in API response
if (data) {
const attachmentColumns = this.model.columns.filter(
(c) => c.uidt === UITypes.Attachment
);
if (attachmentColumns.length) {
if (Array.isArray(data)) {
data = data.map((d) =>
this._convertAttachmentType(attachmentColumns, d)
);
} else {
this._convertAttachmentType(attachmentColumns, data);
}
}
}
return data;
}
}
function extractSortsObject(

7
packages/nocodb/src/lib/meta/api/swagger/helpers/getSwaggerColumnMetas.ts

@ -40,6 +40,12 @@ export default async (
case UITypes.Rollup:
field.type = 'number';
break;
case UITypes.Attachment:
field.type = 'array';
field.items = {
$ref: `#/components/schemas/Attachment`,
};
break;
default:
field.virtual = false;
SwaggerTypes.setSwaggerType(c, field, dbType);
@ -58,4 +64,5 @@ export interface SwaggerColumn {
virtual?: boolean;
$ref?: any;
column: Column;
items?: any;
}

21
packages/nocodb/src/lib/meta/api/swagger/helpers/swagger-base.json

@ -34,6 +34,27 @@
}
}
},
"Attachment": {
"title": "Attachment",
"type": "object",
"properties": {
"mimetype": {
"type": "string"
},
"size": {
"type": "integer"
},
"title": {
"type": "string"
},
"url": {
"type": "string"
},
"icon": {
"type": "string"
}
}
},
"Groupby": {
"title": "Groupby",
"type": "object",

9
packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts

@ -94,7 +94,14 @@ async function getSampleColumnValue(column: Column): Promise<any> {
break;
case UITypes.Attachment:
{
return '[{"url":"https://nocodb.com/dummy.png","title":"image.png","mimetype":"image/png","size":0}]';
return [
{
url: 'https://nocodb.com/dummy.png',
title: 'image.png',
mimetype: 'image/png',
size: 0,
},
];
}
break;
case UITypes.Checkbox:

14
packages/nocodb/src/lib/models/Column.ts

@ -5,7 +5,7 @@ import RollupColumn from './RollupColumn';
import SelectOption from './SelectOption';
import Model from './Model';
import NocoCache from '../cache/NocoCache';
import { ColumnType, UITypes } from 'nocodb-sdk';
import { AllowedColumnTypesForQrCode, ColumnType, UITypes } from 'nocodb-sdk';
import {
CacheDelDirection,
CacheGetType,
@ -950,17 +950,7 @@ export default class Column<T = any> implements ColumnType {
}
// get qr code columns and delete if target type is not supported by QR code column type
// TODO: consider to extraxt the list of allowedColumnTypesForQrValue into a global one,
// ideally in sdk sine it's also used in FE
const allowedColumnTypesForQrValue = [
UITypes.Formula,
UITypes.SingleLineText,
UITypes.LongText,
UITypes.PhoneNumber,
UITypes.URL,
UITypes.Email,
];
if (!allowedColumnTypesForQrValue.includes(updateObj.uidt)) {
if (!AllowedColumnTypesForQrCode.includes(updateObj.uidt)) {
const qrCodeCols = await ncMeta.metaList2(
null,
null,

6
packages/nocodb/src/lib/models/SelectOption.ts

@ -77,9 +77,9 @@ export default class SelectOption {
return options?.length
? {
options: options.map(
({ created_at, updated_at, ...c }) => new SelectOption(c)
),
options: options
.map(({ created_at, updated_at, ...c }) => new SelectOption(c))
.sort((x, y) => x.order - y.order),
}
: null;
}

53
scripts/cypress/cypress.json

@ -1,53 +0,0 @@
{
"baseUrl": "http://localhost:3000/",
"testFiles": [
"test/restTableOps.js",
"test/restViews.js",
"test/restRoles.js",
"test/restMisc.js",
"test/xcdb-restTableOps.js",
"test/xcdb-restViews.js",
"test/xcdb-restRoles.js",
"test/xcdb-restMisc.js",
"test/pg-restTableOps.js",
"test/pg-restViews.js",
"test/pg-restRoles.js",
"test/pg-restMisc.js",
"test/quickTest.js",
"test/db-independent.js"
],
"defaultCommandTimeout": 13000,
"pageLoadTimeout": 600000,
"viewportWidth": 1980,
"viewportHeight": 1000,
"video": false,
"retries": 0,
"screenshotOnRunFailure": true,
"numTestsKeptInMemory": 0,
"experimentalInteractiveRunEvents": true,
"env": {
"testMode": [
{ "apiType": "rest", "dbType": "xcdb" },
{ "apiType": "rest", "dbType": "mysql" },
{ "apiType": "rest", "dbType": "postgres" }
],
"db": {
"host": "127.0.0.1",
"user": "root",
"password": "password"
},
"screenshot": false,
"airtable": {
"apiKey": "keyn1MR87qgyUsYg4",
"sharedBase": "https://airtable.com/shr4z0qmh6dg5s3eB"
}
},
"fixturesFolder": "scripts/cypress/fixtures",
"integrationFolder": "scripts/cypress/integration",
"pluginsFile": "scripts/cypress/plugins/index.js",
"screenshotsFolder": "scripts/cypress/screenshots",
"videosFolder": "scripts/cypress/videos",
"downloadsFolder": "scripts/cypress/downloads",
"supportFile": "scripts/cypress/support/index.js",
"chromeWebSecurity": false
}

67
scripts/cypress/docker-compose-cypress.yml

@ -1,67 +0,0 @@
version: "3.5"
# https://github.com/docker-library/mysql/issues/149
# disabling default sql-mode set to only_full_group_by
services:
xc-mysql-sakila-db:
network_mode: host
image: mysql:8.0
command: mysqld --sql_mode=""
restart: always
environment:
MYSQL_ROOT_PASSWORD: password
volumes:
- ../../packages/nocodb/tests/mysql-sakila-db:/docker-entrypoint-initdb.d
# xc-cypress-nocodb:
# network_mode: host
# image: node:14-alpine
# environment:
# - EE=true
# volumes:
# - ./packages/nocodb:/home/app
# command:
# - /bin/sh
# - -c
# - |
# echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
# # cp -r /home/app1/ /home/app/
# rm /home/app/package-lock.json
# rm /home/app/noco.db
# cd /home/app/ && npm i && EE=true npm run run
# # cd /home/app/ && npm i && EE=true npm run watch:run
# xc-cypress-nc-gui:
# network_mode: host
# image: node:14-alpine
# environment:
# - HOST=0.0.0.0
# - PORT=3000
# - EE=true
# volumes:
# - ./packages/nc-gui:/home/app
# - ./packages/nc-lib-gui:/home/nc-lib-gui
# command:
# - /bin/sh
# - -c
# - |
# echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
# apk --update --no-cache add git
# # cp -r /home/app1/ /home/app/
# rm /home/app/package-lock.json
# cd /home/app/ && npm i && npm run dev
# # cd /home/app/ && npm i && NODE_ENV=development npm run build && npm start

17
scripts/cypress/docker-compose-pg-cy-quick.yml

@ -1,17 +0,0 @@
version: "2.1"
services:
pg96:
image: postgres:9.6
restart: always
environment:
POSTGRES_PASSWORD: password
ports:
- 5432:5432
volumes:
- ../../packages/nocodb/tests/pg-cy-quick:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

17
scripts/cypress/docker-compose-pg.yml

@ -1,17 +0,0 @@
version: "2.1"
services:
pg96:
image: postgres:9.6
restart: always
environment:
POSTGRES_PASSWORD: password
ports:
- 5432:5432
volumes:
- ../../packages/nocodb/tests/pg-sakila-db:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

5
scripts/cypress/fixtures/example.json

@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

BIN
scripts/cypress/fixtures/images/avatar-1.JPG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

BIN
scripts/cypress/fixtures/images/avatar-2.JPG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

BIN
scripts/cypress/fixtures/images/avatar-3.JPG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

BIN
scripts/cypress/fixtures/images/avatar-4.JPG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

BIN
scripts/cypress/fixtures/images/avatar-5.JPG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

BIN
scripts/cypress/fixtures/images/avatar-6.JPG

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

5
scripts/cypress/fixtures/sampleFiles/1.json

@ -1,5 +0,0 @@
{
"country": "Afghanistan",
"city": ["Kabul"]
}

4
scripts/cypress/fixtures/sampleFiles/2.json

@ -1,4 +0,0 @@
{
"country": "Algeria",
"city": ["Batna", "Bchar", "Skikda"]
}

4
scripts/cypress/fixtures/sampleFiles/3.json

@ -1,4 +0,0 @@
{
"country": "Americal Samoa",
"city": ["Tafuna"]
}

4
scripts/cypress/fixtures/sampleFiles/4.json

@ -1,4 +0,0 @@
{
"country": "Angola",
"city": ["Benguela", "Namibe"]
}

4
scripts/cypress/fixtures/sampleFiles/5.json

@ -1,4 +0,0 @@
{
"country": "Anguilla",
"city": ["South Hill"]
}

4
scripts/cypress/fixtures/sampleFiles/6.json

@ -1,4 +0,0 @@
{
"country": "Argentina",
"city": ["Almirante Brown", "Avellaneda", "Beha Blanca", "Crdoba"]
}

BIN
scripts/cypress/fixtures/sampleFiles/Financial Sample.xlsx

Binary file not shown.

16
scripts/cypress/fixtures/sampleFiles/iFrame.html

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<iframe
class="nc-embed"
src="http://localhost:3000/#/nc/base/7d4b551c-b5e0-41c9-a87b-f3984c21d2c7?embed"
frameborder="0"
width="100%"
height="700"
style="background: transparent; "></iframe>
</body>
</html>

BIN
scripts/cypress/fixtures/sampleFiles/sample.xlsx

Binary file not shown.

BIN
scripts/cypress/fixtures/sampleFiles/simple.xlsx

Binary file not shown.

1250
scripts/cypress/fixtures/sqlite-sakila/regenFiles/index.js

File diff suppressed because it is too large Load Diff

854
scripts/cypress/fixtures/sqlite-sakila/regenFiles/package-lock.json generated

@ -1,854 +0,0 @@
{
"name": "nc-xcdb",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
"integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
"optional": true
},
"@mapbox/node-pre-gyp": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz",
"integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==",
"requires": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
}
},
"@npmcli/fs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
"integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==",
"optional": true,
"requires": {
"@gar/promisify": "^1.0.1",
"semver": "^7.3.5"
}
},
"@npmcli/move-file": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
"integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
"optional": true,
"requires": {
"mkdirp": "^1.0.4",
"rimraf": "^3.0.2"
}
},
"@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
"optional": true
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"requires": {
"debug": "4"
}
},
"agentkeepalive": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz",
"integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==",
"optional": true,
"requires": {
"debug": "^4.1.0",
"depd": "^1.1.2",
"humanize-ms": "^1.2.1"
}
},
"aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
"optional": true,
"requires": {
"clean-stack": "^2.0.0",
"indent-string": "^4.0.0"
}
},
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"cacache": {
"version": "15.3.0",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
"integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==",
"optional": true,
"requires": {
"@npmcli/fs": "^1.0.0",
"@npmcli/move-file": "^1.0.1",
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"glob": "^7.1.4",
"infer-owner": "^1.0.4",
"lru-cache": "^6.0.0",
"minipass": "^3.1.1",
"minipass-collect": "^1.0.2",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.2",
"mkdirp": "^1.0.3",
"p-map": "^4.0.0",
"promise-inflight": "^1.0.1",
"rimraf": "^3.0.2",
"ssri": "^8.0.1",
"tar": "^6.0.2",
"unique-filename": "^1.1.1"
}
},
"chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
},
"clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
"optional": true
},
"color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
"optional": true
},
"detect-libc": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
"integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w=="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"optional": true,
"requires": {
"iconv-lite": "^0.6.2"
}
},
"env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"optional": true
},
"err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"optional": true
},
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"requires": {
"minipass": "^3.0.0"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"requires": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
}
},
"glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.2.10",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
"optional": true
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
"optional": true
},
"http-proxy-agent": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
"integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
"optional": true,
"requires": {
"@tootallnate/once": "1",
"agent-base": "6",
"debug": "4"
}
},
"https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"requires": {
"agent-base": "6",
"debug": "4"
}
},
"humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
"optional": true,
"requires": {
"ms": "^2.0.0"
}
},
"iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"optional": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"optional": true
},
"indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"optional": true
},
"infer-owner": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
"integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
"optional": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
"optional": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"is-lambda": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
"integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=",
"optional": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"optional": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"requires": {
"semver": "^6.0.0"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
}
}
},
"make-fetch-happen": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
"integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==",
"optional": true,
"requires": {
"agentkeepalive": "^4.1.3",
"cacache": "^15.2.0",
"http-cache-semantics": "^4.1.0",
"http-proxy-agent": "^4.0.1",
"https-proxy-agent": "^5.0.0",
"is-lambda": "^1.0.1",
"lru-cache": "^6.0.0",
"minipass": "^3.1.3",
"minipass-collect": "^1.0.2",
"minipass-fetch": "^1.3.2",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.4",
"negotiator": "^0.6.2",
"promise-retry": "^2.0.1",
"socks-proxy-agent": "^6.0.0",
"ssri": "^8.0.0"
}
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minipass": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz",
"integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==",
"requires": {
"yallist": "^4.0.0"
}
},
"minipass-collect": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
"integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
"optional": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-fetch": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz",
"integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==",
"optional": true,
"requires": {
"encoding": "^0.1.12",
"minipass": "^3.1.0",
"minipass-sized": "^1.0.3",
"minizlib": "^2.0.0"
}
},
"minipass-flush": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
"optional": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-pipeline": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
"integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
"optional": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-sized": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
"integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
"optional": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
}
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"optional": true
},
"node-addon-api": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"node-gyp": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
"integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==",
"optional": true,
"requires": {
"env-paths": "^2.2.0",
"glob": "^7.1.4",
"graceful-fs": "^4.2.6",
"make-fetch-happen": "^9.1.0",
"nopt": "^5.0.0",
"npmlog": "^6.0.0",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.2",
"which": "^2.0.2"
},
"dependencies": {
"are-we-there-yet": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz",
"integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==",
"optional": true,
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
}
},
"gauge": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
"integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
"optional": true,
"requires": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.3",
"console-control-strings": "^1.1.0",
"has-unicode": "^2.0.1",
"signal-exit": "^3.0.7",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.5"
}
},
"npmlog": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
"integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
"optional": true,
"requires": {
"are-we-there-yet": "^3.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^4.0.3",
"set-blocking": "^2.0.0"
}
}
}
},
"nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"requires": {
"abbrev": "1"
}
},
"npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"requires": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
"optional": true,
"requires": {
"aggregate-error": "^3.0.0"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
"optional": true
},
"promise-retry": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
"optional": true,
"requires": {
"err-code": "^2.0.2",
"retry": "^0.12.0"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
"optional": true
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"glob": "^7.1.3"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"optional": true
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"requires": {
"lru-cache": "^6.0.0"
}
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"optional": true
},
"socks": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz",
"integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==",
"optional": true,
"requires": {
"ip": "^1.1.5",
"smart-buffer": "^4.2.0"
}
},
"socks-proxy-agent": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz",
"integrity": "sha512-wWqJhjb32Q6GsrUqzuFkukxb/zzide5quXYcMVpIjxalDBBYy2nqKCFQ/9+Ie4dvOYSQdOk3hUlZSdzZOd3zMQ==",
"optional": true,
"requires": {
"agent-base": "^6.0.2",
"debug": "^4.3.3",
"socks": "^2.6.2"
}
},
"sqlite3": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.3.tgz",
"integrity": "sha512-/cDwes7XtTOtKH5zYeJSuiavuaJ6jXxPjebw9lDFxBAwR/DvP0tnJ5MPZQ3zpnNzJBa1G6mPTpB+5O1T+AoSdQ==",
"requires": {
"@mapbox/node-pre-gyp": "^1.0.0",
"node-addon-api": "^4.2.0",
"node-gyp": "8.x",
"tar": "^6.1.11"
}
},
"ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
"integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
"optional": true,
"requires": {
"minipass": "^3.1.1"
}
},
"string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
}
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
},
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"requires": {
"ansi-regex": "^5.0.1"
}
},
"tar": {
"version": "6.1.11",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
"integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==",
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"unique-filename": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
"integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
"optional": true,
"requires": {
"unique-slug": "^2.0.0"
}
},
"unique-slug": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
"integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
"optional": true,
"requires": {
"imurmurhash": "^0.1.4"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"optional": true,
"requires": {
"isexe": "^2.0.0"
}
},
"wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"requires": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
}

14
scripts/cypress/fixtures/sqlite-sakila/regenFiles/package.json

@ -1,14 +0,0 @@
{
"name": "nc-xcdb",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"sqlite3": "^5.0.3"
}
}

6
scripts/cypress/fixtures/sqlite-sakila/regenFiles/recreate_sakila_sqlite.sh

@ -1,6 +0,0 @@
#!/bin/bash
set -v
rm sakila.db
sqlite3 sakila.db < ./sqlite-sakila-schema.sql
sqlite3 sakila.db < ./sqlite-sakila-insert-data.sql

45
scripts/cypress/fixtures/sqlite-sakila/regenFiles/sqlite-sakila-delete-data.sql

@ -1,45 +0,0 @@
/*
Sakila for SQLite is a port of the Sakila example database available for MySQL, which was originally developed by Mike Hillyer of the MySQL AB documentation team.
This project is designed to help database administrators to decide which database to use for development of new products
The user can run the same SQL against different kind of databases and compare the performance
License: BSD
Copyright DB Software Laboratory
http://www.etl-tools.com
*/
-- Delete data
DELETE FROM payment
;
DELETE FROM rental
;
DELETE FROM customer
;
DELETE FROM film_category
;
DELETE FROM film_text
;
DELETE FROM film_actor
;
DELETE FROM inventory
;
DELETE FROM film
;
DELETE FROM category
;
DELETE FROM staff
;
DELETE FROM store
;
DELETE FROM actor
;
DELETE FROM address
;
DELETE FROM city
;
DELETE FROM country
;
DELETE FROM language
;

70
scripts/cypress/fixtures/sqlite-sakila/regenFiles/sqlite-sakila-drop-objects.sql

@ -1,70 +0,0 @@
/*
Sakila for SQLite is a port of the Sakila example database available for MySQL, which was originally developed by Mike Hillyer of the MySQL AB documentation team.
This project is designed to help database administrators to decide which database to use for development of new products
The user can run the same SQL against different kind of databases and compare the performance
License: BSD
Copyright DB Software Laboratory
http://www.etl-tools.com
*/
-- Drop Views
DROP VIEW customer_list
;
DROP VIEW film_list
;
--DROP VIEW nicer_but_slower_film_list;
DROP VIEW sales_by_film_category
;
DROP VIEW sales_by_store
;
DROP VIEW staff_list
;
-- Drop Tables
DROP TABLE payment
;
DROP TABLE rental
;
DROP TABLE inventory
;
DROP TABLE film_text
;
DROP TABLE film_category
;
DROP TABLE film_actor
;
DROP TABLE film
;
DROP TABLE language
;
DROP TABLE customer
;
DROP TABLE actor
;
DROP TABLE category
;
DROP TABLE store
;
DROP TABLE address
;
DROP TABLE staff
;
DROP TABLE city
;
DROP TABLE country
;
-- Procedures and views
--drop procedure film_in_stock;
--drop procedure film_not_in_stock;
--drop function get_customer_balance;
--drop function inventory_held_by_customer;
--drop function inventory_in_stock;
--drop procedure rewards_report;

231504
scripts/cypress/fixtures/sqlite-sakila/regenFiles/sqlite-sakila-insert-data.sql

File diff suppressed because it is too large Load Diff

647
scripts/cypress/fixtures/sqlite-sakila/regenFiles/sqlite-sakila-schema.sql

@ -1,647 +0,0 @@
/*
Sakila for SQLite is a port of the Sakila example database available for MySQL, which was originally developed by Mike Hillyer of the MySQL AB documentation team.
This project is designed to help database administrators to decide which database to use for development of new products
The user can run the same SQL against different kind of databases and compare the performance
License: BSD
Copyright DB Software Laboratory
http://www.etl-tools.com
*/
--
-- Table structure for table actor
--
--DROP TABLE actor;
BEGIN TRANSACTION;
CREATE TABLE actor (
actor_id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
first_name VARCHAR(45) NOT NULL,
last_name VARCHAR(45) NOT NULL,
last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
)
;
CREATE INDEX idx_actor_last_name ON actor(last_name)
;
CREATE TRIGGER actor_trigger_ai AFTER INSERT ON actor
BEGIN
UPDATE actor SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER actor_trigger_au AFTER UPDATE ON actor
BEGIN
UPDATE actor SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table country
--
CREATE TABLE country (
country_id INTEGER NOT NULL,
country VARCHAR(50) NOT NULL,
last_update TIMESTAMP,
PRIMARY KEY (country_id)
)
;
CREATE TRIGGER country_trigger_ai AFTER INSERT ON country
BEGIN
UPDATE country SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER country_trigger_au AFTER UPDATE ON country
BEGIN
UPDATE country SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table city
--
CREATE TABLE city (
city_id INTEGER NOT NULL,
city VARCHAR(50) NOT NULL,
country_id INTEGER NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (city_id),
CONSTRAINT fk_city_country FOREIGN KEY (country_id) REFERENCES country (country_id) ON DELETE NO ACTION ON UPDATE CASCADE
)
;
CREATE INDEX idx_fk_country_id ON city(country_id)
;
CREATE TRIGGER city_trigger_ai AFTER INSERT ON city
BEGIN
UPDATE city SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER city_trigger_au AFTER UPDATE ON city
BEGIN
UPDATE city SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table address
--
CREATE TABLE address (
address_id INTEGER NOT NULL,
address VARCHAR(50) NOT NULL,
address2 VARCHAR(50) DEFAULT NULL,
district VARCHAR(20) NOT NULL,
city_id INTEGER NOT NULL,
postal_code VARCHAR(10) DEFAULT NULL,
phone VARCHAR(20) NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (address_id),
CONSTRAINT fk_address_city FOREIGN KEY (city_id) REFERENCES city (city_id) ON DELETE NO ACTION ON UPDATE CASCADE
)
;
CREATE INDEX idx_fk_city_id ON address(city_id)
;
CREATE TRIGGER address_trigger_ai AFTER INSERT ON address
BEGIN
UPDATE address SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER address_trigger_au AFTER UPDATE ON address
BEGIN
UPDATE address SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table language
--
CREATE TABLE language (
language_id INTEGER NOT NULL ,
name CHAR(20) NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (language_id)
)
;
CREATE TRIGGER language_trigger_ai AFTER INSERT ON language
BEGIN
UPDATE language SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER language_trigger_au AFTER UPDATE ON language
BEGIN
UPDATE language SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table category
--
CREATE TABLE category (
category_id INTEGER NOT NULL,
name VARCHAR(25) NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (category_id)
);
CREATE TRIGGER category_trigger_ai AFTER INSERT ON category
BEGIN
UPDATE category SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER category_trigger_au AFTER UPDATE ON category
BEGIN
UPDATE category SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table customer
--
CREATE TABLE customer (
customer_id INTEGER NOT NULL,
store_id INTEGER NOT NULL,
first_name VARCHAR(45) NOT NULL,
last_name VARCHAR(45) NOT NULL,
email VARCHAR(50) DEFAULT NULL,
address_id INTEGER NOT NULL,
active CHAR(1) DEFAULT 'Y' NOT NULL,
create_date TIMESTAMP NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (customer_id),
CONSTRAINT fk_customer_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT fk_customer_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE NO ACTION ON UPDATE CASCADE
)
;
CREATE INDEX idx_customer_fk_store_id ON customer(store_id)
;
CREATE INDEX idx_customer_fk_address_id ON customer(address_id)
;
CREATE INDEX idx_customer_last_name ON customer(last_name)
;
CREATE TRIGGER customer_trigger_ai AFTER INSERT ON customer
BEGIN
UPDATE customer SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER customer_trigger_au AFTER UPDATE ON customer
BEGIN
UPDATE customer SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table film
--
CREATE TABLE film (
film_id INTEGER NOT NULL,
title VARCHAR(255) NOT NULL,
description BLOB SUB_TYPE TEXT DEFAULT NULL,
release_year VARCHAR(4) DEFAULT NULL,
language_id INTEGER NOT NULL,
original_language_id INTEGER DEFAULT NULL,
rental_duration INTEGER DEFAULT 3 NOT NULL,
rental_rate DECIMAL(4,2) DEFAULT 4.99 NOT NULL,
length INTEGER DEFAULT NULL,
replacement_cost DECIMAL(5,2) DEFAULT 19.99 NOT NULL,
rating VARCHAR(10) DEFAULT 'G',
special_features VARCHAR(100) DEFAULT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (film_id),
CONSTRAINT CHECK_special_features CHECK(special_features is null or
special_features like '%Trailers%' or
special_features like '%Commentaries%' or
special_features like '%Deleted Scenes%' or
special_features like '%Behind the Scenes%'),
CONSTRAINT CHECK_special_rating CHECK(rating in ('G','PG','PG-13','R','NC-17')),
CONSTRAINT fk_film_language FOREIGN KEY (language_id) REFERENCES language (language_id) ,
CONSTRAINT fk_film_language_original FOREIGN KEY (original_language_id) REFERENCES language (language_id)
)
;
CREATE INDEX idx_fk_language_id ON film(language_id)
;
CREATE INDEX idx_fk_original_language_id ON film(original_language_id)
;
CREATE TRIGGER film_trigger_ai AFTER INSERT ON film
BEGIN
UPDATE film SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER film_trigger_au AFTER UPDATE ON film
BEGIN
UPDATE film SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table film_actor
--
CREATE TABLE film_actor (
actor_id INTEGER NOT NULL,
film_id INTEGER NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (actor_id,film_id),
CONSTRAINT fk_film_actor_actor FOREIGN KEY (actor_id) REFERENCES actor (actor_id) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT fk_film_actor_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE NO ACTION ON UPDATE CASCADE
)
;
CREATE INDEX idx_fk_film_actor_film ON film_actor(film_id)
;
CREATE INDEX idx_fk_film_actor_actor ON film_actor(actor_id)
;
CREATE TRIGGER film_actor_trigger_ai AFTER INSERT ON film_actor
BEGIN
UPDATE film_actor SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER film_actor_trigger_au AFTER UPDATE ON film_actor
BEGIN
UPDATE film_actor SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table film_category
--
CREATE TABLE film_category (
film_id INTEGER NOT NULL,
category_id INTEGER NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (film_id, category_id),
CONSTRAINT fk_film_category_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT fk_film_category_category FOREIGN KEY (category_id) REFERENCES category (category_id) ON DELETE NO ACTION ON UPDATE CASCADE
)
;
CREATE INDEX idx_fk_film_category_film ON film_category(film_id)
;
CREATE INDEX idx_fk_film_category_category ON film_category(category_id)
;
CREATE TRIGGER film_category_trigger_ai AFTER INSERT ON film_category
BEGIN
UPDATE film_category SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER film_category_trigger_au AFTER UPDATE ON film_category
BEGIN
UPDATE film_category SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table film_text
--
CREATE TABLE film_text (
film_id INTEGER NOT NULL,
title VARCHAR(255) NOT NULL,
description BLOB SUB_TYPE TEXT,
PRIMARY KEY (film_id)
)
;
--
-- Table structure for table inventory
--
CREATE TABLE inventory (
inventory_id INTEGER NOT NULL,
film_id INTEGER NOT NULL,
store_id INTEGER NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (inventory_id),
CONSTRAINT fk_inventory_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT fk_inventory_film FOREIGN KEY (film_id) REFERENCES film (film_id) ON DELETE NO ACTION ON UPDATE CASCADE
)
;
CREATE INDEX idx_fk_film_id ON inventory(film_id)
;
CREATE INDEX idx_fk_film_id_store_id ON inventory(store_id,film_id)
;
CREATE TRIGGER inventory_trigger_ai AFTER INSERT ON inventory
BEGIN
UPDATE inventory SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER inventory_trigger_au AFTER UPDATE ON inventory
BEGIN
UPDATE inventory SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table staff
--
CREATE TABLE staff (
staff_id INTEGER NOT NULL,
first_name VARCHAR(45) NOT NULL,
last_name VARCHAR(45) NOT NULL,
address_id INTEGER NOT NULL,
picture BLOB DEFAULT NULL,
email VARCHAR(50) DEFAULT NULL,
store_id INTEGER NOT NULL,
active INTEGER DEFAULT 1 NOT NULL,
username VARCHAR(16) NOT NULL,
password VARCHAR(40) DEFAULT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (staff_id),
CONSTRAINT fk_staff_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE NO ACTION ON UPDATE CASCADE,
CONSTRAINT fk_staff_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE NO ACTION ON UPDATE CASCADE
)
;
CREATE INDEX idx_fk_staff_store_id ON staff(store_id)
;
CREATE INDEX idx_fk_staff_address_id ON staff(address_id)
;
CREATE TRIGGER staff_trigger_ai AFTER INSERT ON staff
BEGIN
UPDATE staff SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER staff_trigger_au AFTER UPDATE ON staff
BEGIN
UPDATE staff SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table store
--
CREATE TABLE store (
store_id INTEGER NOT NULL,
manager_staff_id INTEGER NOT NULL,
address_id INTEGER NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (store_id),
CONSTRAINT fk_store_staff FOREIGN KEY (manager_staff_id) REFERENCES staff (staff_id) ,
CONSTRAINT fk_store_address FOREIGN KEY (address_id) REFERENCES address (address_id)
)
;
CREATE INDEX idx_store_fk_manager_staff_id ON store(manager_staff_id)
;
CREATE INDEX idx_fk_store_address ON store(address_id)
;
CREATE TRIGGER store_trigger_ai AFTER INSERT ON store
BEGIN
UPDATE store SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER store_trigger_au AFTER UPDATE ON store
BEGIN
UPDATE store SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- Table structure for table payment
--
CREATE TABLE payment (
payment_id INTEGER NOT NULL,
customer_id INTEGER NOT NULL,
staff_id INTEGER NOT NULL,
rental_id INTEGER DEFAULT NULL,
amount DECIMAL(5,2) NOT NULL,
payment_date TIMESTAMP NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (payment_id),
CONSTRAINT fk_payment_rental FOREIGN KEY (rental_id) REFERENCES rental (rental_id) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT fk_payment_customer FOREIGN KEY (customer_id) REFERENCES customer (customer_id) ,
CONSTRAINT fk_payment_staff FOREIGN KEY (staff_id) REFERENCES staff (staff_id)
)
;
CREATE INDEX idx_fk_staff_id ON payment(staff_id)
;
CREATE INDEX idx_fk_customer_id ON payment(customer_id)
;
CREATE TRIGGER payment_trigger_ai AFTER INSERT ON payment
BEGIN
UPDATE payment SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER payment_trigger_au AFTER UPDATE ON payment
BEGIN
UPDATE payment SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TABLE rental (
rental_id INTEGER NOT NULL,
rental_date TIMESTAMP NOT NULL,
inventory_id INTEGER NOT NULL,
customer_id INTEGER NOT NULL,
return_date TIMESTAMP DEFAULT NULL,
staff_id INTEGER NOT NULL,
last_update TIMESTAMP NOT NULL,
PRIMARY KEY (rental_id),
CONSTRAINT fk_rental_staff FOREIGN KEY (staff_id) REFERENCES staff (staff_id) ,
CONSTRAINT fk_rental_inventory FOREIGN KEY (inventory_id) REFERENCES inventory (inventory_id) ,
CONSTRAINT fk_rental_customer FOREIGN KEY (customer_id) REFERENCES customer (customer_id)
)
;
CREATE INDEX idx_rental_fk_inventory_id ON rental(inventory_id)
;
CREATE INDEX idx_rental_fk_customer_id ON rental(customer_id)
;
CREATE INDEX idx_rental_fk_staff_id ON rental(staff_id)
;
CREATE UNIQUE INDEX idx_rental_uq ON rental (rental_date,inventory_id,customer_id)
;
CREATE TRIGGER rental_trigger_ai AFTER INSERT ON rental
BEGIN
UPDATE rental SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
CREATE TRIGGER rental_trigger_au AFTER UPDATE ON rental
BEGIN
UPDATE rental SET last_update = DATETIME('NOW') WHERE rowid = new.rowid;
END
;
--
-- View structure for view customer_list
--
CREATE VIEW customer_list
AS
SELECT cu.customer_id AS ID,
cu.first_name||' '||cu.last_name AS name,
a.address AS address,
a.postal_code AS zip_code,
a.phone AS phone,
city.city AS city,
country.country AS country,
case when cu.active=1 then 'active' else '' end AS notes,
cu.store_id AS SID
FROM customer AS cu JOIN address AS a ON cu.address_id = a.address_id JOIN city ON a.city_id = city.city_id
JOIN country ON city.country_id = country.country_id
;
--
-- View structure for view film_list
--
CREATE VIEW film_list
AS
SELECT film.film_id AS FID,
film.title AS title,
film.description AS description,
category.name AS category,
film.rental_rate AS price,
film.length AS length,
film.rating AS rating,
actor.first_name||' '||actor.last_name AS actors
FROM category LEFT JOIN film_category ON category.category_id = film_category.category_id LEFT JOIN film ON film_category.film_id = film.film_id
JOIN film_actor ON film.film_id = film_actor.film_id
JOIN actor ON film_actor.actor_id = actor.actor_id
;
--
-- View structure for view staff_list
--
CREATE VIEW staff_list
AS
SELECT s.staff_id AS ID,
s.first_name||' '||s.last_name AS name,
a.address AS address,
a.postal_code AS zip_code,
a.phone AS phone,
city.city AS city,
country.country AS country,
s.store_id AS SID
FROM staff AS s JOIN address AS a ON s.address_id = a.address_id JOIN city ON a.city_id = city.city_id
JOIN country ON city.country_id = country.country_id
;
--
-- View structure for view sales_by_store
--
CREATE VIEW sales_by_store
AS
SELECT
s.store_id
,c.city||','||cy.country AS store
,m.first_name||' '||m.last_name AS manager
,SUM(p.amount) AS total_sales
FROM payment AS p
INNER JOIN rental AS r ON p.rental_id = r.rental_id
INNER JOIN inventory AS i ON r.inventory_id = i.inventory_id
INNER JOIN store AS s ON i.store_id = s.store_id
INNER JOIN address AS a ON s.address_id = a.address_id
INNER JOIN city AS c ON a.city_id = c.city_id
INNER JOIN country AS cy ON c.country_id = cy.country_id
INNER JOIN staff AS m ON s.manager_staff_id = m.staff_id
GROUP BY
s.store_id
, c.city||','||cy.country
, m.first_name||' '||m.last_name
;
--
-- View structure for view sales_by_film_category
--
-- Note that total sales will add up to >100% because
-- some titles belong to more than 1 category
--
CREATE VIEW sales_by_film_category
AS
SELECT
c.name AS category
, SUM(p.amount) AS total_sales
FROM payment AS p
INNER JOIN rental AS r ON p.rental_id = r.rental_id
INNER JOIN inventory AS i ON r.inventory_id = i.inventory_id
INNER JOIN film AS f ON i.film_id = f.film_id
INNER JOIN film_category AS fc ON f.film_id = fc.film_id
INNER JOIN category AS c ON fc.category_id = c.category_id
GROUP BY c.name
;
--
-- View structure for view actor_info
--
/*
CREATE VIEW actor_info
AS
SELECT
a.actor_id,
a.first_name,
a.last_name,
GROUP_CONCAT(DISTINCT CONCAT(c.name, ': ',
(SELECT GROUP_CONCAT(f.title ORDER BY f.title SEPARATOR ', ')
FROM sakila.film f
INNER JOIN sakila.film_category fc
ON f.film_id = fc.film_id
INNER JOIN sakila.film_actor fa
ON f.film_id = fa.film_id
WHERE fc.category_id = c.category_id
AND fa.actor_id = a.actor_id
)
)
ORDER BY c.name SEPARATOR '; ')
AS film_info
FROM sakila.actor a
LEFT JOIN sakila.film_actor fa
ON a.actor_id = fa.actor_id
LEFT JOIN sakila.film_category fc
ON fa.film_id = fc.film_id
LEFT JOIN sakila.category c
ON fc.category_id = c.category_id
GROUP BY a.actor_id, a.first_name, a.last_name;
*/
-- TO DO PROCEDURES
-- TO DO TRIGGERS
END TRANSACTION;

BIN
scripts/cypress/fixtures/sqlite-sakila/sakila.db

Binary file not shown.

261
scripts/cypress/integration/common/00_pre_configurations.js

@ -1,261 +0,0 @@
// Cypress test suite: project pre-configurations
//
import { loginPage, projectsPage } from "../../support/page_objects/navigation";
import { mainPage } from "../../support/page_objects/mainPage";
import {
staticProjects,
roles,
isTestSuiteActive,
getCurrentMode,
isXcdb,
setProjectString,
} from "../../support/page_objects/projectConstants";
function prepareSqliteQuery(projId) {
let sqliteQuery = [
`ALTER TABLE "actor" RENAME TO "${projId}actor"`,
`ALTER TABLE "address" RENAME TO "${projId}address"`,
`ALTER TABLE "category" RENAME TO "${projId}category"`,
`ALTER TABLE "city" RENAME TO "${projId}city"`,
`ALTER TABLE "country" RENAME TO "${projId}country"`,
`ALTER TABLE "customer" RENAME TO "${projId}customer"`,
`ALTER TABLE "film" RENAME TO "${projId}film"`,
`ALTER TABLE "film_actor" RENAME TO "${projId}film_actor"`,
`ALTER TABLE "film_category" RENAME TO "${projId}film_category"`,
`ALTER TABLE "film_text" RENAME TO "${projId}film_text"`,
`ALTER TABLE "inventory" RENAME TO "${projId}inventory"`,
`ALTER TABLE "language" RENAME TO "${projId}language"`,
`ALTER TABLE "payment" RENAME TO "${projId}payment"`,
`ALTER TABLE "rental" RENAME TO "${projId}rental"`,
`ALTER TABLE "staff" RENAME TO "${projId}staff"`,
`ALTER TABLE "store" RENAME TO "${projId}store"`,
`CREATE VIEW ${projId}customer_list
AS
SELECT cu.customer_id AS ID,
cu.first_name||' '||cu.last_name AS name,
a.address AS address,
a.postal_code AS zip_code,
a.phone AS phone,
"${projId}city".city AS city,
"${projId}country".country AS country,
case when cu.active=1 then 'active' else '' end AS notes,
cu.store_id AS SID
FROM "${projId}customer" AS cu JOIN "${projId}address" AS a ON cu.address_id = a.address_id JOIN "${projId}city" ON a.city_id = "${projId}city".city_id
JOIN "${projId}country" ON "${projId}city".country_id = "${projId}country".country_id`,
`CREATE VIEW ${projId}film_list
AS
SELECT "${projId}film".film_id AS FID,
"${projId}film".title AS title,
"${projId}film".description AS description,
"${projId}category".name AS category,
"${projId}film".rental_rate AS price,
"${projId}film".length AS length,
"${projId}film".rating AS rating,
"${projId}actor".first_name||' '||"${projId}actor".last_name AS actors
FROM "${projId}category" LEFT JOIN "${projId}film_category" ON "${projId}category".category_id = "${projId}film_category".category_id LEFT JOIN "${projId}film" ON "${projId}Film_category".film_id = "${projId}film".film_id
JOIN "${projId}film_actor" ON "${projId}film".film_id = "${projId}film_actor".film_id
JOIN "${projId}actor" ON "${projId}film_actor".actor_id = "${projId}actor".actor_id`,
`CREATE VIEW ${projId}sales_by_film_category
AS
SELECT
c.name AS category
, SUM(p.amount) AS total_sales
FROM "${projId}payment" AS p
INNER JOIN "${projId}rental" AS r ON p.rental_id = r.rental_id
INNER JOIN "${projId}inventory" AS i ON r.inventory_id = i.inventory_id
INNER JOIN "${projId}film" AS f ON i.film_id = f.film_id
INNER JOIN "${projId}film_category" AS fc ON f.film_id = fc.film_id
INNER JOIN "${projId}category" AS c ON fc.category_id = c.category_id
GROUP BY c.name`,
`CREATE VIEW ${projId}sales_by_store
AS
SELECT
s.store_id
,c.city||','||cy.country AS store
,m.first_name||' '||m.last_name AS manager
,SUM(p.amount) AS total_sales
FROM "${projId}payment" AS p
INNER JOIN "${projId}rental" AS r ON p.rental_id = r.rental_id
INNER JOIN "${projId}inventory" AS i ON r.inventory_id = i.inventory_id
INNER JOIN "${projId}store" AS s ON i.store_id = s.store_id
INNER JOIN "${projId}address" AS a ON s.address_id = a.address_id
INNER JOIN "${projId}city" AS c ON a.city_id = c.city_id
INNER JOIN "${projId}country" AS cy ON c.country_id = cy.country_id
INNER JOIN "${projId}staff" AS m ON s.manager_staff_id = m.staff_id
GROUP BY
s.store_id
, c.city||','||cy.country
, m.first_name||' '||m.last_name`,
`CREATE VIEW ${projId}staff_list
AS
SELECT s.staff_id AS ID,
s.first_name||' '||s.last_name AS name,
a.address AS address,
a.postal_code AS zip_code,
a.phone AS phone,
"${projId}city".city AS city,
"${projId}country".country AS country,
s.store_id AS SID
FROM "${projId}staff" AS s JOIN "${projId}address" AS a ON s.address_id = a.address_id JOIN "${projId}city" ON a.city_id = "${projId}city".city_id
JOIN "${projId}country" ON "${projId}city".country_id = "${projId}country".country_id`,
// below two are dummy entries to ensure view record exists
`CREATE VIEW ${projId}actor_info
AS
SELECT s.staff_id AS ID,
s.first_name||' '||s.last_name AS name,
a.address AS address,
a.postal_code AS zip_code,
a.phone AS phone,
"${projId}city".city AS city,
"${projId}country".country AS country,
s.store_id AS SID
FROM "${projId}staff" AS s JOIN "${projId}address" AS a ON s.address_id = a.address_id JOIN "${projId}city" ON a.city_id = "${projId}city".city_id
JOIN "${projId}country" ON "${projId}city".country_id = "${projId}country".country_id`,
`CREATE VIEW ${projId}nice_but_slower_film_list
AS
SELECT s.staff_id AS ID,
s.first_name||' '||s.last_name AS name,
a.address AS address,
a.postal_code AS zip_code,
a.phone AS phone,
"${projId}city".city AS city,
"${projId}country".country AS country,
s.store_id AS SID
FROM "${projId}staff" AS s JOIN "${projId}address" AS a ON s.address_id = a.address_id JOIN "${projId}city" ON a.city_id = "${projId}city".city_id
JOIN "${projId}country" ON "${projId}city".country_id = "${projId}country".country_id`,
// `CREATE VIEW ${projId}actor_info
// AS
// SELECT
// a.actor_id AS actor_id,
// a.first_name AS first_name,
// a.last_name AS last_name,
// GROUP_CONCAT(DISTINCT CONCAT(c.name,
// ': ',
// (SELECT
// GROUP_CONCAT(f.title
// ORDER BY f.title ASC
// SEPARATOR ', ')
// FROM
// ((${projId}film f
// JOIN ${projId}film_category fc ON ((f.film_id = fc.film_id)))
// JOIN ${projId}film_actor fa ON ((f.film_id = fa.film_id)))
// WHERE
// ((fc.category_id = c.category_id)
// AND (fa.actor_id = a.actor_id))))
// ORDER BY c.name ASC
// SEPARATOR '; ') AS ${projId}film_info
// FROM
// (((actor a
// LEFT JOIN ${projId}film_actor fa ON ((a.actor_id = fa.actor_id)))
// LEFT JOIN ${projId}film_category fc ON ((fa.film_id = fc.film_id)))
// LEFT JOIN ${projId}category c ON ((fc.category_id = c.category_id)))
// GROUP BY a.actor_id , a.first_name , a.last_name`,
];
return sqliteQuery;
}
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`Project pre-configurations`, () => {
before(() => {
cy.fileHook();
});
it("Admin SignUp", () => {
cy.task("log", "This will be output to the terminal");
loginPage.signUp(roles.owner.credentials);
});
function cy_createProjectBlock(proj, apiType, dbType) {
// click home button
cy.getSettled(".nc-noco-brand-icon").click();
cy.get(".ant-table-content").then((obj) => {
// if project already created, open
// else, create a new one
if (true == obj[0].innerHTML.includes(proj.basic.name)) {
projectsPage.openProject(proj.basic.name);
let projId;
if (dbType === "xcdb") {
let query = `SELECT prefix from nc_projects_v2 where title = "sampleREST"; `;
cy.task("sqliteExecReturnValue", query).then((resolve) => {
cy.log(resolve);
projId = resolve.prefix;
setProjectString(projId);
cy.log(projId);
});
}
} else {
projectsPage.createProject(proj.basic, proj.config);
if (dbType === "xcdb") {
// store base URL- to re-visit and delete form view later
let projId;
cy.url()
.then((url) => {
// project prefix code can include "_"
// projId = url.split("_")[1].split("?")[0];
let startIdx = url.indexOf("_");
let endIdx = url.indexOf("?");
projId = url.slice(startIdx + 1, endIdx);
let query = `SELECT prefix from nc_projects_v2 where title = "sampleREST"; `;
cy.task("sqliteExecReturnValue", query)
.then((resolve) => {
cy.log(resolve);
projId = resolve.prefix;
cy.log(projId);
setProjectString(projId);
})
.then(() => {
let query = prepareSqliteQuery(projId);
for (let i = 0; i < query.length; i++) {
cy.task("sqliteExec", query[i]);
cy.wait(1000);
}
});
})
.then(() => {
cy.log(projId);
mainPage.openMetaTab();
mainPage.metaSyncValidate(
`${projId}actor`,
`New table, New relation added`
);
mainPage.closeMetaTab();
});
}
}
});
}
const createProject = (proj) => {
it(`Create ${proj.basic.name} project`, () => {
if (dbType === "postgres") {
// wait for docker compose to start
cy.task("pgExecTest", `SELECT 1+1`, { timeout: 120000 }).then(() =>
cy_createProjectBlock(proj, apiType, dbType)
);
} else {
cy_createProjectBlock(proj, apiType, dbType);
}
// close team & auth tab
// wait for tab to be rendered completely
cy.wait(2000);
cy.closeTableTab();
// first instance of updating local storage information
cy.saveLocalStorage();
});
};
if ("xcdb" === dbType) {
createProject(staticProjects.sampleREST);
} else if (dbType === "mysql") {
createProject(staticProjects.externalREST);
} else if (dbType === "postgres") {
createProject(staticProjects.pgExternalREST);
}
});
};

106
scripts/cypress/integration/common/1a_table_operations.js

@ -1,106 +0,0 @@
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
import { mainPage, settingsPage } from "../../support/page_objects/mainPage";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${
dbType === "xcdb" ? "Meta - " : ""
}${apiType.toUpperCase()} api - Table`, () => {
// before(() => {
// // standalone test
// // loginPage.loginAndOpenProject(apiType, dbType);
//
// // open a table to work on views
// //
// // cy.restoreLocalStorage();
// });
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
// after(() => {
// });
const name = "tablex";
// create a new random table
it("Create Table", () => {
cy.createTable(name);
});
// delete newly created table
it("Delete Table", () => {
cy.deleteTable(name, dbType);
});
const getAuditCell = (row, col) => {
return cy.get("tbody > tr").eq(row).find("td.ant-table-cell").eq(col);
};
describe("Check Audit Tab Cells", () => {
it("Open Audit tab", () => {
// http://localhost:8080/api/v1/db/meta/projects/p_bxp57hmks0n5o2/audits?offset=0&limit=25
cy.intercept("/**/audits?offset=*&limit=*").as("waitForPageLoad");
// mainPage.navigationDraw(mainPage.AUDIT).click();
settingsPage.openMenu(settingsPage.AUDIT);
// wait for column headers to appear
//
cy.get("thead > tr > th.ant-table-cell").should("have.length", 5);
cy.wait("@waitForPageLoad");
// Audit table entries
// [Header] Operation Type, Operation Sub Type, Description, User, Created
// [0] TABLE, DELETED, delete table table-x, user@nocodb.com, ...
// [1] TABLE, Created, created table table-x, user@nocodb.com, ...
getAuditCell(0, 0).contains("TABLE").should("exist");
getAuditCell(0, 1).contains("DELETED").should("exist");
getAuditCell(0, 3).contains("user@nocodb.com").should("exist");
getAuditCell(1, 0).contains("TABLE").should("exist");
getAuditCell(1, 1).contains("CREATED").should("exist");
getAuditCell(1, 3).contains("user@nocodb.com").should("exist");
});
after(() => {
settingsPage.closeMenu();
})
})
it("Table Rename operation", () => {
cy.get(".nc-project-tree-tbl-City").should("exist").click();
cy.renameTable("City", "CityX");
// verify
// 1. Table name in project tree has changed
// cy.get(".nc-tbl-title").contains("CityX").should("exist");
cy.get(".nc-project-tree-tbl-CityX").should("exist");
// 2. Table tab name has changed
cy.get(`.ant-tabs-tab:contains('CityX'):visible`).should("exist");
// Wait for Grid to render
cy.gridWait(25);
// 3. contents of the table are valid
mainPage
.getCell(`City`, 1)
.contains("A Corua (La Corua)")
.should("exist");
cy.closeTableTab("CityX");
// revert re-name operation to not impact rest of test suite
cy.renameTable("CityX", "City");
});
});
};

200
scripts/cypress/integration/common/1b_table_column_operations.js

@ -1,200 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import {
isTestSuiteActive,
isXcdb,
} from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
function addNewRow(index, cellValue) {
mainPage.addNewRowExpand("tablex");
// cy.get("#data-table-form-Title > input").first().type(cellValue);
cy.get(".nc-expand-col-Title")
.find(".nc-cell > input")
.should("exist")
.first()
.clear()
.type(cellValue);
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find("button")
.contains("Save row")
.should("exist")
.click();
cy.toastWait("updated successfully");
cy.get("body").type("{esc}");
mainPage.getCell("Title", index).contains(cellValue).should("exist");
}
describe(`${apiType.toUpperCase()} api - Table Column`, () => {
const name = "tablex";
const colName = "column_name_a";
const updatedColName = "updated_column_name";
const randVal = "Test@1234.com";
const updatedRandVal = "Updated@1234.com";
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
it("Create Table Column", () => {
cy.createTable(name);
mainPage.addColumn(colName, name);
});
// edit the newly created column
it("Edit table column - change datatype", () => {
if (!isXcdb()) {
cy.get(`th:contains(${colName}) .nc-icon.ant-dropdown-trigger`)
.trigger("mouseover", { force: true })
.click({ force: true });
// cy.get(".nc-column-edit").click();
// cy.get(".nc-column-edit").should("not.be.visible");
cy.getActiveMenu(".nc-dropdown-column-operations")
.find(".nc-column-edit")
.click();
cy.inputHighlightRenderWait();
// change column type and verify
// cy.get(".nc-column-type-input").last().click();
cy.getActiveMenu(".nc-dropdown-edit-column")
.find(".nc-column-type-input")
.last()
.click();
cy.getActiveSelection(".nc-dropdown-column-type")
.find(".ant-select-item-option")
.contains("LongText")
.click();
cy.getActiveMenu(".nc-dropdown-edit-column")
.find(".ant-btn-primary:visible")
.contains("Save")
.click();
cy.toastWait("Column updated");
}
});
// edit the newly created column
it("Edit table column - rename", () => {
cy.get(`th:contains(${colName}) .nc-icon.ant-dropdown-trigger`)
.trigger("mouseover", { force: true })
.click({ force: true });
// cy.get(".nc-column-edit").click();
// cy.get(".nc-column-edit").should("not.be.visible");
cy.getActiveMenu(".nc-dropdown-column-operations")
.find(".nc-column-edit")
.click();
// rename column and verify
cy.getActiveMenu(".nc-dropdown-edit-column")
.find("input.nc-column-name-input", { timeout: 3000 })
.should("exist")
.clear()
.type(updatedColName);
// cy.get(".ant-btn-primary:visible").contains("Save").click();
cy.getActiveMenu(".nc-dropdown-edit-column")
.find(".ant-btn-primary:visible")
.contains("Save")
.click();
cy.toastWait("Column updated");
cy.get(`th:contains(${colName})`).should("not.exist");
cy.get(`th:contains(${updatedColName})`).should("exist");
});
// delete the newly created column
it("Delete table column", () => {
mainPage.deleteColumn(updatedColName);
});
it("Add new row", () => {
addNewRow(1, randVal);
});
it("Update row", () => {
mainPage
.getRow(1)
.find(".nc-row-no")
.should("exist")
.eq(0)
.trigger("mouseover", { force: true })
.get(".nc-row-expand")
.click({ force: true });
// wait for page render to complete
cy.get('button:contains("Save row"):visible').should("exist");
cy.get(".nc-expand-col-Title")
.find(".nc-cell > input")
.should("exist")
.first()
.clear()
.type(updatedRandVal);
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find("button")
.contains("Save row")
.should("exist")
.click();
// partial toast message
cy.toastWait("updated successfully");
cy.get("body").type("{esc}");
mainPage.getCell("Title", 1).contains(randVal).should("not.exist");
mainPage.getCell("Title", 1).contains(updatedRandVal).should("exist");
});
it("Delete Row", () => {
mainPage
.getCell("Title", 1)
.contains(updatedRandVal)
.rightclick({ force: true });
// delete row
cy.getActiveMenu(".nc-dropdown-grid-context-menu")
.find('.ant-dropdown-menu-item:contains("Delete Row")')
.first()
.click({ force: true });
cy.get("td").contains(randVal).should("not.exist");
});
it("Select all row check-box validation", () => {
// add multiple rows
addNewRow(1, "a1");
addNewRow(2, "a2");
addNewRow(3, "a3");
addNewRow(4, "a4");
addNewRow(5, "a5");
cy.get(".nc-no-label")
.should("exist")
.eq(0)
.trigger("mouseover", { force: true });
cy.get(".ant-checkbox").should("exist").eq(0).click({ force: true });
// delete selected rows
mainPage.getCell("Title", 3).rightclick({ force: true });
cy.getActiveMenu(".nc-dropdown-grid-context-menu")
.contains("Delete Selected Rows")
.click({ force: true });
// verify if everything is wiped off
mainPage.getCell("Title", 1).contains("a1").should("not.exist");
// clean-up
cy.deleteTable(name, dbType);
});
});
};

143
scripts/cypress/integration/common/1c_sql_view.js

@ -1,143 +0,0 @@
import {
isTestSuiteActive,
isXcdb,
} from "../../support/page_objects/projectConstants";
import { mainPage } from "../../support/page_objects/mainPage";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} SQL Views`, () => {
// Run once before test- create project (rest/graphql)
//
// before(() => {
// cy.fileHook();
// mainPage.tabReset();
// });
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
it(`XCDB: SQL View Column operations`, () => {
// Open one of the views & verify validity of first two entries
if (isXcdb()) {
cy.openViewsTab("CustomerList", 25);
// Record-1 validation
mainPage.getCell(`ID`, 1).contains("1").should("exist");
mainPage.getCell(`Name`, 1).contains("MARY SMITH").should("exist");
mainPage
.getCell(`Address`, 1)
.contains("1913 Hanoi Way")
.should("exist");
mainPage.getCell(`ZipCode`, 1).contains("35200").should("exist");
// Record-2 validation
mainPage.getCell(`ID`, 2).contains("2").should("exist");
mainPage
.getCell(`Name`, 2)
.contains("PATRICIA JOHNSON")
.should("exist");
mainPage
.getCell(`Address`, 2)
.contains("1121 Loja Avenue")
.should("exist");
mainPage.getCell(`ZipCode`, 2).contains("17886").should("exist");
// Column operations: Hide
mainPage.hideField(`ZipCode`);
mainPage.unhideField(`ZipCode`);
// Column operations: Sort
mainPage.sortField("Name", "Z → A");
mainPage.getCell(`Name`, 1).contains("ZACHARY HITE").should("exist");
mainPage.clearSort();
// Column operations: Filter
mainPage.filterField("Name", "is like", "MARY");
mainPage.getCell(`Name`, 1).contains("MARY SMITH").should("exist");
mainPage.filterReset();
cy.closeViewsTab("CustomerList");
}
});
it(`SQL View Column operations`, () => {
if (!isXcdb()) {
// Open one of the views & verify validity of first two entries
cy.openViewsTab("ActorInfo", 25);
// Record-1 validation
mainPage.getCell(`ActorId`, 1).contains("1").should("exist");
mainPage.getCell(`FirstName`, 1).contains("PENELOPE").should("exist");
mainPage.getCell(`LastName`, 1).contains("GUINESS").should("exist");
mainPage
.getCell(`FilmInfo`, 1)
.contains("Animation: ANACONDA CONFESSIONS")
.should("exist");
// Record-2 validation
mainPage.getCell(`ActorId`, 2).contains("2").should("exist");
mainPage.getCell(`FirstName`, 2).contains("NICK").should("exist");
mainPage.getCell(`LastName`, 2).contains("WAHLBERG").should("exist");
mainPage
.getCell(`FilmInfo`, 2)
.contains("Action: BULL SHAWSHANK")
.should("exist");
// Column operations: Hide
mainPage.hideField("FilmInfo");
mainPage.unhideField("FilmInfo");
// Column operations: Sort
mainPage.sortField("FirstName", "Z → A");
mainPage.getCell(`FirstName`, 1).contains("ZERO").should("exist");
mainPage.clearSort();
// Column operations: Filter
mainPage.filterField("FirstName", "is like", "PENELOPE");
mainPage.getCell(`FirstName`, 1).contains("PENELOPE").should("exist");
mainPage.filterReset();
cy.closeViewsTab("ActorInfo");
}
});
it.skip(`SQL View List`, () => {
// confirm if other views exist
//
cy.openViewsTab("CustomerList", 25);
cy.closeViewsTab("CustomerList");
cy.openViewsTab("FilmList", 25);
cy.closeViewsTab("FilmList");
cy.openViewsTab("SalesByFilmCategory", 16);
cy.closeViewsTab("SalesByFilmCategory");
if (!isXcdb()) {
cy.openViewsTab("NicerButSlowerFilmList", 25);
cy.closeViewsTab("NicerButSlowerFilmList");
// SalesByStore && StaffList contain no entries. Hence marking row count to 0
cy.openViewsTab("SalesByStore", 0);
cy.closeViewsTab("SalesByStore");
cy.openViewsTab("StaffList", 0);
cy.closeViewsTab("StaffList");
} else {
cy.openViewsTab("SalesByStore", 2);
cy.closeViewsTab("SalesByStore");
cy.openViewsTab("StaffList", 2);
cy.closeViewsTab("StaffList");
}
});
});
};

147
scripts/cypress/integration/common/1d_pg_table_view_drag_drop_reorder.js

@ -1,147 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import {
isTestSuiteActive,
isXcdb,
getProjectString,
isPostgres,
} from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} Table/view drag-drop reorder`, () => {
function validateTreeField(index, tblName) {
cy.get(`:nth-child(${index}) > .v-list-item__title > .caption`)
.contains(tblName)
.should("exist");
}
before(() => {
cy.fileHook();
mainPage.tabReset();
});
/*
Original order of list items
Actor, Address, Category, City, Country, Customer, FIlm, FilmText, Language, Payment, Rental Staff
ActorInfo, Customer List, Film List, NiceButSlowerFilm List, SalesByFilmCategory, SalesByStore, Staff List
*/
it(`Table & SQL View list, Drag/drop`, () => {
// expand tree-view menu
// cy.get(".nc-project-tree")
// .find(".v-list-item__title:contains(Tables)", { timeout: 10000 })
// .should("exist")
// .first()
// .click({ force: true });
validateTreeField(1, "Actor");
// move Actor field down, above Staff (drag, drop)
cy.get(".nc-child-draggable-icon-Actor").drag(
".nc-child-draggable-icon-Film"
);
validateTreeField(7, "Actor");
// // move ActorInfo (View) field up to first place (drag, drop)
// cy.get(".nc-child-draggable-icon-ActorInfo").drag(
// ".nc-child-draggable-icon-Address"
// );
// validateTreeField(1, "ActorInfo");
// validateTreeField(2, "Address");
// validateTreeField(8, "Actor");
// // restore ActorInfo field (drag, drop)
// cy.get(".nc-child-draggable-icon-ActorInfo").drag(
// ".nc-child-draggable-icon-Staff"
// );
// restore Actor field (drag, drop)
cy.get(".nc-child-draggable-icon-Actor").drag(
".nc-child-draggable-icon-Address"
);
validateTreeField(1, "Actor");
validateTreeField(2, "Address");
// undo project-tree expand operation
cy.get(".nc-project-tree")
.find(".v-list-item__title:contains(Tables)", {
timeout: 10000,
})
.should("exist")
.first()
.click({ force: true });
});
// create new view as specified by 'viewType'
// can be - grid/ gallery/ form
// wait for toast to appear
//
function createView(viewType) {
// click on 'Grid/Gallery' button on Views bar
cy.get(`.nc-create-${viewType}-view`).click();
cy.snipActiveModal(`Modal_createView_${viewType}`);
// Pop up window, click Submit (accepting default name for view)
cy.getActiveModal(".nc-modal-view-create")
.find("button:contains(Submit)")
.click();
cy.toastWait("View created successfully");
}
// verify view 'viewName' to be present at position 'index'
// index starts from 0
function validateViewField(index, viewName) {
cy.get(".nc-view-item.nc-draggable-child")
.eq(index)
.contains(viewName)
.should("exist");
}
it(`View (Gallery/ Grid/ Form) re-order`, () => {
cy.openTableTab("Actor", 25);
// create 3 views, use default names
// Actor1, Actor2, Actor3
createView("grid");
createView("gallery");
createView("form");
// validate position order
validateViewField(0, "Actor");
validateViewField(1, "Actor1");
validateViewField(2, "Actor2");
validateViewField(3, "Actor3");
// move Actor3 field on top (drag, drop)
cy.get(".nc-child-draggable-icon-Actor3").drag(
`.nc-child-draggable-icon-${
isXcdb() ? `${getProjectString()}` : ``
}actor`
);
// validate new position order, Actor3 on top
validateViewField(0, "Actor3");
validateViewField(1, "Actor");
validateViewField(2, "Actor1");
validateViewField(3, "Actor2");
// delete all created views
// click on delete icon (becomes visible on hovering mouse)
cy.get(".nc-view-delete-icon").eq(0).click({ force: true });
cy.toastWait("View deleted successfully");
cy.get(".nc-view-delete-icon").eq(0).click({ force: true });
cy.toastWait("View deleted successfully");
cy.get(".nc-view-delete-icon").eq(0).click({ force: true });
cy.toastWait("View deleted successfully");
// wind up
cy.closeTableTab("Actor");
});
});
};

156
scripts/cypress/integration/common/1d_table_view_drag_drop_reorder.js

@ -1,156 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import {
isTestSuiteActive,
isXcdb,
getProjectString,
} from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} Table/view drag-drop reorder`, () => {
function validateTreeField(index, tblName) {
cy.get(`.nc-project-tree-tbl`)
.eq(index - 1)
.find(".nc-tbl-title")
.contains(tblName)
.should("exist");
}
/*
Original order of list items
Actor, Address, Category, City, Country, Customer, FIlm, FilmText, Language, Payment, Rental Staff
ActorInfo, Customer List, Film List, NiceButSlowerFilm List, SalesByFilmCategory, SalesByStore, Staff List
*/
before(() => {
cy.fileHook();
mainPage.tabReset();
});
beforeEach(() => {
cy.fileHook();
});
it(`Table & SQL View list, Drag/drop`, () => {
// expand tree-view menu
// cy.get(".nc-project-tree")
// .find(".v-list-item__title:contains(Tables)", { timeout: 10000 })
// .should("exist")
// .first()
// .click({ force: true });
validateTreeField(1, "Actor");
// move Actor field down, above Staff (drag, drop)
cy.get(".nc-child-draggable-icon-Actor").click({ force: true });
cy.get(".nc-child-draggable-icon-Actor").should("be.visible");
cy.get(".nc-child-draggable-icon-Actor").drag(
".nc-child-draggable-icon-Staff",
{ force: true }
);
validateTreeField(12, "Actor");
// // move ActorInfo (View) field up to first place (drag, drop)
// cy.get(".nc-child-draggable-icon-ActorInfo").drag(
// ".nc-child-draggable-icon-Address"
// );
//
// validateTreeField(1, "ActorInfo");
// validateTreeField(2, "Address");
// validateTreeField(13, "Actor");
//
// // restore ActorInfo field (drag, drop)
// cy.get(".nc-child-draggable-icon-ActorInfo").drag(
// ".nc-child-draggable-icon-Actor"
// );
//
// // restore Actor field (drag, drop)
// cy.get(".nc-child-draggable-icon-Actor").drag(
// ".nc-child-draggable-icon-Address"
// );
//
// validateTreeField(1, "Actor");
// validateTreeField(2, "Address");
// validateTreeField(12, "Staff");
// validateTreeField(13, "ActorInfo");
// validateTreeField(14, "CustomerList");
//
// // undo project-tree expand operation
// cy.get(".nc-project-tree")
// .should("exist")
// .first()
// .click({ force: true });
});
// create new view as specified by 'viewType'
// can be - grid/ gallery/ form
// wait for toast to appear
//
function createView(viewType) {
// click on 'Grid/Gallery' button on Views bar
cy.get(`.nc-create-${viewType}-view`).click();
cy.snipActiveModal(`Modal_createView_${viewType}`);
// Pop up window, click Submit (accepting default name for view)
cy.getActiveModal(".nc-modal-view-create")
.find("button:contains(Submit)")
.click();
cy.toastWait("View created successfully");
}
// verify view 'viewName' to be present at position 'index'
// index starts from 0
function validateViewField(index, viewName) {
cy.get(".nc-view-item.nc-draggable-child")
.eq(index)
.contains(viewName)
.should("exist");
}
// exclude@ncv2: to be investigated & fixed
it.skip(`View (Gallery/ Grid/ Form) re-order`, () => {
cy.openTableTab("Actor", 25);
// create 3 views, use default names
// Actor1, Actor2, Actor3
createView("grid");
createView("gallery");
createView("form");
// validate position order
validateViewField(0, "Actor");
validateViewField(1, "Actor1");
validateViewField(2, "Actor2");
validateViewField(3, "Actor3");
// move Actor3 field on top (drag, drop)
cy.get(".nc-child-draggable-icon-Actor3").drag(
`.nc-child-draggable-icon-${
isXcdb() ? `${getProjectString()}` : ``
}Actor`
);
// validate new position order, Actor3 on top
validateViewField(0, "Actor3");
validateViewField(1, "Actor");
validateViewField(2, "Actor1");
validateViewField(3, "Actor2");
// delete all created views
// click on delete icon (becomes visible on hovering mouse)
cy.get(".nc-view-delete-icon").eq(0).click({ force: true });
cy.toastWait("View deleted successfully");
cy.get(".nc-view-delete-icon").eq(0).click({ force: true });
cy.toastWait("View deleted successfully");
cy.get(".nc-view-delete-icon").eq(0).click({ force: true });
cy.toastWait("View deleted successfully");
// wind up
cy.closeTableTab("Actor");
});
});
};

169
scripts/cypress/integration/common/1e_meta_sync.js

@ -1,169 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { loginPage } from "../../support/page_objects/navigation";
import {
getCurrentMode,
getProjectString,
isTestSuiteActive,
isXcdb,
} from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
let projPrefix = `sakila.`;
let dbCmd = `queryDb`;
let tblDisplayPrefix = ``;
describe(`${apiType.toUpperCase()} api - Meta Sync`, () => {
// Run once before test- create project (rest/graphql)
//
before(() => {
cy.restoreLocalStorage();
if (isXcdb()) {
cy.log(getProjectString());
projPrefix = `${getProjectString()}`;
dbCmd = `sqliteExec`;
tblDisplayPrefix = `${getProjectString()}`;
}
mainPage.openMetaTab();
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
it(`Create table`, () => {
// Create Table
cy.task(
dbCmd,
`CREATE TABLE ${projPrefix}table1 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`
);
cy.task(
dbCmd,
`CREATE TABLE ${projPrefix}table2 (id INT NOT NULL, col1 INT NULL, PRIMARY KEY (id))`
);
mainPage.metaSyncValidate(`${tblDisplayPrefix}table1`, "New table");
});
it(`Add relation`, () => {
// working with relations in sqlite requires table to be deleted & recreated
//
if (!isXcdb()) {
// Add relation (FK)
cy.task(
dbCmd,
`ALTER TABLE ${projPrefix}table1 ADD INDEX fk1_idx (col1 ASC) VISIBLE`
);
cy.task(
dbCmd,
`ALTER TABLE ${projPrefix}table1 ADD CONSTRAINT fk1 FOREIGN KEY (col1) REFERENCES ${projPrefix}table2 (id) ON DELETE NO ACTION ON UPDATE NO ACTION`
);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"New relation added"
);
}
});
it(`Remove relation`, () => {
// working with relations in sqlite requires table to be deleted & recreated
//
if (!isXcdb()) {
// Remove relation (FK)
cy.task(dbCmd, `ALTER TABLE ${projPrefix}table1 DROP FOREIGN KEY fk1`);
cy.task(dbCmd, `ALTER TABLE ${projPrefix}table1 DROP INDEX fk1_idx`);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"Relation removed"
);
}
});
it(`Add column`, () => {
// Add Column
let queryString = `ALTER TABLE ${projPrefix}table1 ADD COLUMN newCol VARCHAR(45) NULL AFTER id`;
if (isXcdb())
queryString = `ALTER TABLE ${projPrefix}table1 ADD COLUMN newCol TEXT NULL`;
cy.task(dbCmd, queryString);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"New column(newCol)"
);
});
it(`Rename column`, () => {
// Rename Column
let queryString = `ALTER TABLE ${projPrefix}table1 CHANGE COLUMN newCol newColName VARCHAR(45) NULL DEFAULT NULL`;
if (isXcdb())
queryString = `ALTER TABLE ${projPrefix}table1 RENAME COLUMN newCol TO newColName`;
cy.task(dbCmd, queryString);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"New column(newColName), Column removed(newCol)"
);
});
it(`Delete column`, () => {
// Remove Column
// to be fixed for SQLITE
if (!isXcdb()) {
cy.task(
dbCmd,
`ALTER TABLE ${projPrefix}table1 DROP COLUMN newColName`
);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"Column removed(newColName)"
);
}
});
it(`Delete table`, () => {
// DROP TABLE
cy.task(dbCmd, `DROP TABLE ${projPrefix}table1`);
cy.task(dbCmd, `DROP TABLE ${projPrefix}table2`);
mainPage.metaSyncValidate(`${tblDisplayPrefix}table1`, "Table removed");
});
it(`Hide, Filter, Sort`, () => {
cy.task(
dbCmd,
`CREATE TABLE ${projPrefix}table1 (id INT NOT NULL, col1 INT NULL, col2 INT NULL, col3 INT NULL, col4 INT NULL, PRIMARY KEY (id))`
);
cy.task(
dbCmd,
`INSERT INTO ${projPrefix}table1 (id, col1, col2, col3, col4) VALUES (1,1,1,1,1), (2,2,2,2,2), (3,3,3,3,3), (4,4,4,4,4), (5,5,5,5,5), (6,6,6,6,6), (7,7,7,7,7), (8,8,8,8,8), (9,9,9,9,9);`
);
mainPage.metaSyncValidate(`${tblDisplayPrefix}table1`, "New table");
mainPage.closeMetaTab();
cy.openTableTab("Table1", 9);
mainPage.hideField("Col1");
mainPage.sortField("Col1", "9 → 1");
mainPage.filterField(`Col1`, ">=", "5");
cy.get(".nc-grid-row").should("have.length", 5);
cy.closeTableTab("Table1");
});
it(`Verify`, () => {
mainPage.openMetaTab();
// Rename Column
let queryString = `ALTER TABLE ${projPrefix}table1 CHANGE COLUMN col1 newCol INT NULL DEFAULT NULL`;
if (isXcdb())
queryString = `ALTER TABLE ${projPrefix}table1 RENAME COLUMN col1 TO newCol`;
cy.task(dbCmd, queryString);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"New column(newCol), Column removed(col1)"
);
mainPage.closeMetaTab();
cy.openTableTab("Table1", 9);
cy.deleteTable("Table1", dbType);
});
});
};

182
scripts/cypress/integration/common/1e_pg_meta_sync.js

@ -1,182 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { loginPage } from "../../support/page_objects/navigation";
import {
getCurrentMode,
getProjectString,
isTestSuiteActive,
isXcdb,
} from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
let projPrefix = `sakila.`;
let dbCmd = `pgExec`;
let tblDisplayPrefix = ``;
describe(`${apiType.toUpperCase()} api - Meta Sync`, () => {
// Run once before test- create project (rest/graphql)
//
before(() => {
cy.restoreLocalStorage();
mainPage.openMetaTab();
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
it(`Create table`, () => {
cy.log("this works");
// Create Table
cy.task(
dbCmd,
`CREATE TABLE table1( id INT NOT NULL, col1 INT NOT NULL, PRIMARY KEY(id))`
);
cy.task(
dbCmd,
`CREATE TABLE table2( id INT NOT NULL, col1 INT NOT NULL, PRIMARY KEY(id))`
);
mainPage.metaSyncValidate(`${tblDisplayPrefix}table1`, "New table");
});
it(`Add relation`, () => {
// working with relations in sqlite requires table to be deleted & recreated
//
// Add relation (FK)
cy.task(
dbCmd,
`ALTER TABLE table1 ADD CONSTRAINT fk_idx FOREIGN KEY (id) REFERENCES table2 (id);`
);
// cy.task(
// dbCmd,
// `ALTER TABLE ${projPrefix}table1 ADD CONSTRAINT fk1 FOREIGN KEY (col1) REFERENCES ${projPrefix}table2 (id) ON DELETE NO ACTION ON UPDATE NO ACTION`
// );
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"New relation added"
);
});
it(`Remove relation`, () => {
// working with relations in sqlite requires table to be deleted & recreated
//
// Remove relation (FK)
cy.task(dbCmd, `ALTER TABLE table1 DROP CONSTRAINT fk_idx`);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"Relation removed"
);
});
it(`Add column`, () => {
// Add Column
let queryString = `ALTER TABLE table1 ADD COLUMN newCol INT`;
cy.task(dbCmd, queryString);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"New column(newcol)"
);
});
it(`Rename column`, () => {
// Rename Column
let queryString = `ALTER TABLE table1 RENAME COLUMN newCol TO newColName`;
cy.task(dbCmd, queryString);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"New column(newcolname), Column removed(newcol)"
);
});
it(`Delete column`, () => {
// Remove Column
cy.task(dbCmd, `ALTER TABLE table1 DROP COLUMN newColName`);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"Column removed(newcolname)"
);
});
it(`Delete table`, () => {
// DROP TABLE
cy.task(dbCmd, `DROP TABLE IF EXISTS table1`);
cy.task(dbCmd, `DROP TABLE IF EXISTS table2`);
mainPage.metaSyncValidate(`${tblDisplayPrefix}table1`, "Table removed");
});
it(`Hide, Filter, Sort`, () => {
// kludge: bulk insert fail.
cy.task(
dbCmd,
`CREATE TABLE table1( id INT NOT NULL, col1 INT NOT NULL, col2 INT NOT NULL, col3 INT NOT NULL, col4 INT NOT NULL, PRIMARY KEY(id))`
);
cy.wait(3000);
cy.task(
dbCmd,
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (1,1,1,1,1)`
);
cy.task(
dbCmd,
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (2,2,2,2,2)`
);
cy.task(
dbCmd,
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (3,3,3,3,3)`
);
cy.task(
dbCmd,
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (4,4,4,4,4)`
);
cy.task(
dbCmd,
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (5,5,5,5,5)`
);
cy.task(
dbCmd,
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (6,6,6,6,6)`
);
cy.task(
dbCmd,
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (7,7,7,7,7)`
);
cy.task(
dbCmd,
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (8,8,8,8,8)`
);
cy.task(
dbCmd,
`INSERT INTO table1 (id, col1, col2, col3, col4) VALUES (9,9,9,9,9)`
);
mainPage.metaSyncValidate(`${tblDisplayPrefix}table1`, "New table");
mainPage.closeMetaTab();
cy.openTableTab("Table1", 9);
mainPage.hideField("Col1");
mainPage.sortField("Col1", "9 → 1");
mainPage.filterField(`Col1`, ">=", "5");
cy.get(".nc-grid-row").should("have.length", 5);
cy.closeTableTab("Table1");
});
it(`Verify`, () => {
mainPage.openMetaTab();
// Rename Column
let queryString = `ALTER TABLE table1 RENAME COLUMN col1 TO newcol`;
cy.task(dbCmd, queryString);
mainPage.metaSyncValidate(
`${tblDisplayPrefix}table1`,
"New column(newcol), Column removed(col1)"
);
mainPage.closeMetaTab();
cy.openTableTab("Table1", 9);
// kludge- delete table triggered post sql backend operations doesnt carry any trigger toast
cy.deleteTable("Table1", "mysql");
});
});
};

118
scripts/cypress/integration/common/2a_table_with_belongs_to_colulmn.js

@ -1,118 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - Table: belongs to, link record`, () => {
// before(() => {
// cy.restoreLocalStorage();
// cy.openTableTab("Country", 25);
// });
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
// after(() => {
// cy.closeTableTab("City");
// });
it("URL validation", () => {
cy.openTableTab("Country", 25);
// column name validation
// cy.get(`.project-tab:contains(Country):visible`).should("exist");
// URL validation
cy.url().should("contain", `table/Country`);
});
it("Grid cell chip content validation", () => {
// grid cell content validation
mainPage
.getCell("City List", 1)
.find(".nc-virtual-cell > .chips-wrapper > .chips > .group > .name")
.contains("Kabul")
.should("exist");
mainPage
.getCell("City List", 2)
.find(".nc-virtual-cell > .chips-wrapper > .chips > .group > .name")
.contains("Batna")
.should("exist");
mainPage
.getCell("City List", 2)
.find(".nc-virtual-cell > .chips-wrapper > .chips > .group > .name")
.contains("Bchar")
.should("exist");
mainPage
.getCell("City List", 2)
.find(".nc-virtual-cell > .chips-wrapper > .chips > .group > .name")
.contains("Skikda")
.should("exist");
});
it("Expand has-many column", () => {
mainPage
.getCell("City List", 1)
.should("exist")
.trigger("mouseover")
.click();
cy.get(".nc-action-icon").eq(0).should("exist").click({ force: true });
});
it("Expand Link record, validate", () => {
cy.getActiveModal(".nc-modal-child-list")
.find("button:contains(Link to 'City')")
.click()
.then(() => {
// Link record form validation
cy.getActiveModal(".nc-modal-link-record")
.contains("Link record")
.should("exist");
cy.getActiveModal(".nc-modal-link-record")
.find(".nc-reload")
.should("exist");
cy.getActiveModal(".nc-modal-link-record")
.find('button:contains("Add new record")')
.should("exist");
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.eq(0)
.contains("A Corua (La Corua)")
.should("exist");
cy.getActiveModal(".nc-modal-link-record")
.find("button.ant-modal-close")
.click();
// .then(() => {
// cy.getActiveModal()
// .find("button.ant-modal-close")
// .click();
// });
});
});
it("Belongs to column, validate", () => {
cy.closeTableTab("Country");
cy.openTableTab("City", 25);
cy.url().should("contain", `table/City`);
// grid cell content validation
mainPage
.getCell("Country", 1)
.find(".nc-virtual-cell > .chips-wrapper > .chips > .group > .name")
.contains("Spain")
.should("exist");
mainPage
.getCell("Country", 2)
.find(".nc-virtual-cell > .chips-wrapper > .chips > .group > .name")
.contains("Saudi Arabia")
.should("exist");
cy.closeTableTab("City");
});
});
};

130
scripts/cypress/integration/common/2b_table_with_m2m_column.js

@ -1,130 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - M2M Column validation`, () => {
// before(() => {
// cy.openTableTab("Actor", 25);
// });
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
// after(() => {
// cy.closeTableTab("Actor");
// });
it("Table column header, URL validation", () => {
cy.openTableTab("Actor", 25);
// column name validation
// cy.get(`.project-tab:contains(Actor):visible`).should("exist");
// URL validation
cy.url().should("contain", `table/Actor`);
});
it("M2m chip content validation on grid", () => {
// grid m2m content validation
mainPage
.getCell("Film List", 1)
.find(".nc-virtual-cell > .chips-wrapper > .chips > .group > .name")
.contains("ACADEMY DINOSAUR")
.should("exist");
mainPage
.getCell("Film List", 1)
.find(".nc-virtual-cell > .chips-wrapper > .chips > .group > .name")
.contains("ANACONDA CONFESSIONS")
.should("exist");
});
it("Expand m2m column", () => {
// expand first row
mainPage
.getCell("Film List", 1)
.should("exist")
.trigger("mouseover")
.get(".nc-action-icon").eq(0).should("exist").click({ force: true });
// GUI-v2 Kludge:
// validations
// cy.getActiveModal().contains("Film").should("exist");
// cy.getActiveModal().find("button.mdi-reload").should("exist");
// cy.getActiveModal()
// .find("button:contains(Link to 'Film')")
// .should("exist");
cy.getActiveModal(".nc-modal-child-list")
.find(".ant-card")
.eq(0)
.contains("ACADEMY DINOSAUR")
.should("exist");
});
it('Expand "Link to" record, validate', () => {
cy.getActiveModal(".nc-modal-child-list")
.find("button:contains(Link to 'Film')")
.click()
.then(() => {
// Link record form validation
cy.getActiveModal(".nc-modal-link-record")
.contains("Link record")
.should("exist");
cy.getActiveModal(".nc-modal-link-record")
.find(".nc-reload")
.should("exist");
cy.getActiveModal(".nc-modal-link-record")
.find('button:contains("Add new record")')
.should("exist");
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.eq(0)
.contains("ACE GOLDFINGER")
.should("exist");
cy.getActiveModal(".nc-modal-link-record")
.find("button.ant-modal-close")
.click();
});
});
it("Expand first linked card, validate", () => {
// expand first row
mainPage
.getCell("Film List", 1)
.should("exist")
.trigger("mouseover")
.get(".nc-action-icon").eq(0).should("exist").click({ force: true });
cy.getActiveModal(".nc-modal-child-list")
.find(".ant-card")
.eq(0)
.contains("ACADEMY DINOSAUR", { timeout: 2000 })
.click()
.then(() => {
// Link card validation
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find(".text-lg")
.contains("ACADEMY DINOSAUR")
.should("exist");
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find('button:contains("Save row")')
.should("exist");
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find('button:contains("Cancel")')
.should("exist");
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find('button:contains("Cancel")')
.should("exist")
.click();
cy.getActiveModal().find("button.ant-modal-close").click();
});
cy.closeTableTab("Actor");
});
});
};

198
scripts/cypress/integration/common/3a_filter_sort_fields_operations.js

@ -1,198 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { loginPage } from "../../support/page_objects/navigation";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - Grid operations`, () => {
// before(() => {
// // loginPage.loginAndOpenProject(apiType, dbType);
//
// // open country table
// cy.openTableTab("Country", 25);
// });
//
// after(() => {
// cy.closeTableTab("Country");
// });
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
it("Check country table - Pagination", () => {
cy.openTableTab("Country", 25);
cy.get(".nc-pagination").should("exist");
// verify > pagination option
mainPage.getPagination(">").click();
mainPage
.getPagination(2)
.should("have.class", "ant-pagination-item-active");
// verify < pagination option
mainPage.getPagination("<").click();
mainPage
.getPagination(1)
.should("have.class", "ant-pagination-item-active");
});
// create new row using + button in header
//
it("Add row using tool header button", () => {
// http://localhost:8080/api/v1/db/meta/audits/comments/count?ids[]=101&ids[]=102&ids[]=103&ids[]=104&ids[]=105&ids[]=106&ids[]=107&ids[]=108&ids[]=109&fk_model_id=md_zfkb9v3mzky958
cy.intercept("/api/v1/db/meta/audits/comments/count*").as(
"waitForPageLoad"
);
// add a row to end of Country table
mainPage.addNewRowExpand("Country");
cy.get(".nc-expand-col-Country")
.find(".nc-cell > input")
.first()
.type("Test Country");
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find(".ant-btn-primary")
.contains("Save row")
.should("exist")
.click();
// cy.get("#data-table-form-Country > input")
// .first()
// .type("Test Country");
// cy.contains("Save row").filter("button").click();
cy.toastWait("updated successfully");
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find(".ant-btn")
.contains("Cancel")
.should("exist")
.click();
// verify
mainPage.getPagination(5).click();
cy.wait("@waitForPageLoad");
mainPage.getCell("Country", 10).contains("Test Country").should("exist");
});
// delete single row
//
it("Delete Row", () => {
// delete row added in previous step
mainPage.getCell("Country", 10).rightclick();
cy.getActiveMenu(".nc-dropdown-grid-context-menu")
.contains("Delete Row")
.click();
// cy.toastWait('Deleted row successfully')
// verify
cy.get(`:nth-child(10) > [data-title="Country"]`).should("not.exist");
mainPage.getPagination(1).click();
});
// create new row using right click menu option
//
it.skip("Add row using rightclick menu option", () => {
// Temporary
mainPage.getPagination(5).click();
mainPage.getCell("Country", 9).rightclick({ force: true });
cy.getActiveMenu(".nc-dropdown-grid-context-menu")
.contains("Insert New Row")
.click({ force: true });
mainPage
.getCell("Country", 10)
.dblclick()
.find("input")
.type("Test Country-1{enter}");
mainPage.getCell("Country", 10).rightclick({ force: true });
cy.getActiveMenu(".nc-dropdown-grid-context-menu")
.contains("Insert New Row")
.click({ force: true });
mainPage
.getCell("Country", 11)
.dblclick()
.find("input")
.type("Test Country-2{enter}");
// GUI-v2 Kludge:
// to move cursor away from input field; enter key is not recognized
// mainPage.getCell("Country", 10).click()
// verify
mainPage
.getCell("Country", 10)
.contains("Test Country-1")
.should("exist");
mainPage
.getCell("Country", 11)
.contains("Test Country-2")
.should("exist");
});
// delete selected rows (multiple)
//
it.skip("Delete Selected", () => {
cy.get(".ant-checkbox").should("exist").eq(10).click({ force: true });
cy.get(".ant-checkbox").should("exist").eq(11).click({ force: true });
mainPage.getCell("Country", 10).rightclick({ force: true });
cy.getActiveMenu(".nc-dropdown-grid-context-menu")
.contains("Delete Selected Rows")
.click({ force: true });
// verify
// mainPage.getCell("Country", 10).should("not.exist");
// mainPage.getCell("Country", 11).should("not.exist");
cy.get(`:nth-child(10) > [data-title="Country"]`).should("not.exist");
cy.get(`:nth-child(11) > [data-title="Country"]`).should("not.exist");
mainPage.getPagination(1).click();
});
it("Enable sort", () => {
mainPage.sortField("Country", "Z → A");
cy.contains("Zambia").should("exist");
});
it("Disable sort", () => {
mainPage.clearSort();
cy.contains("Zambia").should("not.exist");
});
it("Hide field", () => {
mainPage.hideField("LastUpdate");
});
it("Show field", () => {
mainPage.unhideField("LastUpdate");
});
it("Create Filter", () => {
mainPage.filterField("Country", "is equal", "India");
// cy.get("td:contains(India)").should("exist");
mainPage.getCell("Country", 1).contains("India").should("exist");
});
it("Delete Filter", () => {
// remove sort and check
mainPage.filterReset();
mainPage.getCell("Country", 1).contains("India").should("not.exist");
// cy.contains("td:contains(India)").should("not.exist");
cy.closeTableTab("Country");
});
});
};

369
scripts/cypress/integration/common/3b_formula_column.js

@ -1,369 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { loginPage } from "../../support/page_objects/navigation";
import {
isTestSuiteActive,
isXcdb,
} from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - FORMULA`, () => {
// Run once before test- create project (rest/graphql)
//
// before(() => {
// // loginPage.loginAndOpenProject(apiType, dbType)
// cy.openTableTab("City", 25);
// });
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
// after(() => {
// cy.closeTableTab("City");
// });
// Given rowname & expected result for first 10 entries, validate
// NOTE: Scroll issue with Cypress automation, to fix
// validating partial data, row number 5 to 9
//
const rowValidation = (rowName, result) => {
// scroll back
// cy.get(
// `tbody > :nth-child(1) > [data-col="City"]`
// ).scrollIntoView();
// for (let i = 0; i < 10; i++)
for (let i = 3; i < 5; i++)
mainPage
.getCell(rowName, i + 1)
.contains(result[i].toString())
.should("exist");
// cy.get(`tbody > :nth-child(${i + 1}) > [data-col="${rowName}"]`)
// .contains(result[i].toString())
// .should("exist");
};
// Routine to create a new look up column
//
const addFormulaBasedColumn = (columnName, formula) => {
cy.get(".nc-grid tr > th:last .nc-icon").click({
force: true,
});
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find("input.nc-column-name-input", { timeout: 3000 })
.should("exist")
.clear()
.type(columnName);
// cy.get(".nc-column-type-input").last().click().type("Formula");
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".nc-column-type-input")
.last()
.click()
.type("Formula");
cy.getActiveSelection(".nc-dropdown-column-type")
.find(".ant-select-item-option")
.contains("Formula")
.click();
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find("textarea.nc-formula-input")
.click()
.type(formula, { parseSpecialCharSequences: false });
// cy.get(".ant-btn-primary").contains("Save").should('exist').click();
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".ant-btn-primary:visible")
.contains("Save")
.click();
// cy.toastWait(`Column created`);
cy.closeTableTab("City");
cy.openTableTab("City", 25);
cy.get(`th[data-title="${columnName}"]`).should("exist");
};
// routine to delete column
//
const deleteColumnByName = (columnName) => {
mainPage.deleteColumn(columnName);
};
// routine to edit column
//
const editColumnByName = (oldName, newName, newFormula) => {
cy.get(`th:contains(${oldName}) .nc-icon.ant-dropdown-trigger`)
.trigger("mouseover", { force: true })
.click({ force: true });
// cy.get(".nc-column-edit").click();
// cy.get(".nc-column-edit").should("not.be.visible");
cy.getActiveMenu(".nc-dropdown-column-operations")
.find(".nc-column-edit")
.click();
if (newName !== oldName) {
cy.getActiveMenu(".nc-dropdown-edit-column")
.find("input.nc-column-name-input", { timeout: 3000 })
.should("exist")
.clear()
.type(newName);
}
cy.get("textarea.nc-formula-input")
.click()
.clear()
.type(newFormula, { parseSpecialCharSequences: false });
cy.get(".ant-form-item-explain-error").should('not.exist');
cy.get(".ant-btn-primary").contains("Save").should("exist").click();
// cy.toastWait(`Column created`);
if (newName !== oldName) {
cy.get(`th[data-title="${oldName}"]`).should("not.exist");
}
cy.get(`th[data-title="${newName}"]`).should("exist");
};
// routine to edit a column with Circular Reference
//
const editCircularColumnByName = (columnName, newFormula) => {
cy.get(`th:contains(${columnName}) .nc-icon.ant-dropdown-trigger`)
.trigger("mouseover", { force: true })
.click({ force: true });
cy.getActiveMenu(".nc-dropdown-column-operations")
.find(".nc-column-edit")
.click();
cy.getActiveMenu(".nc-dropdown-edit-column")
.find("input.nc-column-name-input", { timeout: 3000 })
.should("exist");
cy.get("textarea.nc-formula-input")
.click()
.clear()
.type(newFormula, { parseSpecialCharSequences: false });
// clicking the Save button, should NOT submit the form
cy.get(".ant-btn-primary").contains("Save").click();
// therefore we can see the error
cy.get(".ant-form-item-explain-error").contains("Can’t save field because it causes a circular reference");
// then close the form without saving
cy.get(".ant-btn").contains("Cancel").click();
};
///////////////////////////////////////////////////
// Test case
// On City table (from Sakila DB), first 10 entries recorded here for verification
let cityId = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let countryId = [87, 82, 101, 60, 97, 31, 107, 44, 44, 50];
let city = [
"A corua (La Corua)",
"Abha",
"Abu Dhabi",
"Acua",
"Adana",
"Addis Abeba",
"Aden",
"Adoni",
"Ahmadnagar",
"Akishima",
];
// Temporary locally computed expected results
let RESULT_STRING = [];
let RESULT_MATH_0 = [];
let RESULT_MATH_1 = [];
let RESULT_MATH_2 = [];
let RESULT_MATH_3 = [];
let RESULT_WEEKDAY_0 = [];
let RESULT_WEEKDAY_1 = [];
let RESULT_CIRC_REF_0 = [];
let RESULT_CIRC_REF_1 = [];
let RESULT_CIRC_REF_2 = [];
let RESULT_CIRC_REF_0_FINAL = [];
let RESULT_CIRC_REF_2_FINAL = [];
for (let i = 0; i < 10; i++) {
// CONCAT, LOWER, UPPER, TRIM
RESULT_STRING[i] = `${city[i].toUpperCase()}${city[
i
].toLowerCase()}trimmed`;
// ADD, AVG, LEN
RESULT_MATH_0[i] =
cityId[i] +
countryId[i] +
(cityId[i] + countryId[i]) / 2 +
city[i].length;
// CEILING, FLOOR, ROUND, MOD, MIN, MAX
RESULT_MATH_1[i] =
Math.ceil(1.4) +
Math.floor(1.6) +
Math.round(2.5) +
(cityId[i] % 3) +
Math.min(cityId[i], countryId[i]) +
Math.max(cityId[i], countryId[i]);
RESULT_MATH_2[i] =
1.23 + Math.min(2.34, 3.45) + Math.max(2.34, 3.45);
// LOG, EXP, POWER, SQRT
// only integer verification being computed, hence trunc
RESULT_MATH_3[i] = Math.trunc(
Math.log(cityId[i]) +
Math.exp(cityId[i]) +
Math.pow(cityId[i], 3) +
Math.sqrt(countryId[i])
);
// WEEKDAY: starts from Monday
RESULT_WEEKDAY_0[i] = 1;
// WEEKDAY: starts from Sunday
RESULT_WEEKDAY_1[i] = 2;
RESULT_CIRC_REF_0[i] = city[i]
RESULT_CIRC_REF_1[i] = city[i]
RESULT_CIRC_REF_2[i] = city[i] + city[i]
RESULT_CIRC_REF_0_FINAL[i] = city[i] + city[i]
RESULT_CIRC_REF_2_FINAL[i] = city[i] + city[i] + city[i] + city[i]
}
it("Formula: ADD, AVG, LEN", () => {
cy.openTableTab("City", 25);
addFormulaBasedColumn(
"NC_MATH_0",
"ADD({CityId}, {CountryId}) + AVG({CityId}, {CountryId}) + LEN({City})"
);
rowValidation("NC_MATH_0", RESULT_MATH_0);
});
it.skip("Formula: WEEKDAY", () => {
editColumnByName("NC_MATH_0", "NC_WEEKDAY_0", `WEEKDAY("2022-07-19")`);
rowValidation("NC_WEEKDAY_0", RESULT_WEEKDAY_0);
editColumnByName(
"NC_WEEKDAY_0",
"NC_WEEKDAY_1",
`WEEKDAY("2022-07-19", "sunday")`
);
rowValidation("NC_WEEKDAY_1", RESULT_WEEKDAY_1);
});
it("Formula: CONCAT, LOWER, UPPER, TRIM", () => {
editColumnByName(
// "NC_WEEKDAY_1",
"NC_MATH_0",
"NC_STR_1",
`CONCAT(UPPER({City}), LOWER({City}), TRIM(' trimmed '))`
);
rowValidation("NC_STR_1", RESULT_STRING);
});
it("Formula: CEILING, FLOOR, ROUND, MOD, MIN, MAX", () => {
editColumnByName(
"NC_STR_1",
"NC_MATH_1",
`CEILING(1.4) + FLOOR(1.6) + ROUND(2.5) + MOD({CityId}, 3) + MIN({CityId}, {CountryId}) + MAX({CityId}, {CountryId})`
);
rowValidation("NC_MATH_1", RESULT_MATH_1);
});
it("Formula: ROUND with decimals, MIN, MAX", () => {
editColumnByName(
"NC_MATH_1",
"NC_MATH_2",
`ROUND(1.2345, 2) + MIN(2.34, 3.45) + MAX(2.34, 3.45)`
);
rowValidation("NC_MATH_2", RESULT_MATH_2)
})
it("Formula: LOG, EXP, POWER, SQRT", () => {
// if (!isXcdb()) {
if (dbType === "mysql") {
// SQLITE doesnt support LOG, EXP, POWER SQRT construct
editColumnByName(
"NC_MATH_2",
"NC_MATH_3",
`LOG({CityId}) + EXP({CityId}) + POWER({CityId}, 3) + SQRT({CountryId})`
);
rowValidation("NC_MATH_3", RESULT_MATH_3);
}
});
it("Formula: NOW, EDIT & Delete column", () => {
// if (!isXcdb()) editColumnByName("NC_MATH_2", "NC_NOW", `NOW()`);
if (dbType === "mysql") editColumnByName("NC_MATH_3", "NC_NOW", `NOW()`);
else editColumnByName("NC_MATH_2", "NC_NOW", `NOW()`);
deleteColumnByName("NC_NOW");
cy.closeTableTab("City");
});
it("Formula: Circular references", () => {
cy.openTableTab("City", 25);
addFormulaBasedColumn(
"NC_CIRC_REF_0",
"{City}"
);
addFormulaBasedColumn(
"NC_CIRC_REF_1",
"{NC_CIRC_REF_0}"
);
editCircularColumnByName(
"NC_CIRC_REF_0",
"{NC_CIRC_REF_1}"
);
deleteColumnByName("NC_CIRC_REF_1");
deleteColumnByName("NC_CIRC_REF_0");
cy.closeTableTab("City");
});
it("Formula: Duplicated dependencies (neighbours)", () => {
cy.openTableTab("City", 25);
addFormulaBasedColumn(
"NC_CIRC_REF_0",
"{City}"
);
addFormulaBasedColumn(
"NC_CIRC_REF_1",
"{NC_CIRC_REF_0}"
);
addFormulaBasedColumn(
"NC_CIRC_REF_2",
"CONCAT({NC_CIRC_REF_1},{NC_CIRC_REF_1})"
);
rowValidation("NC_CIRC_REF_0", RESULT_CIRC_REF_0);
rowValidation("NC_CIRC_REF_1", RESULT_CIRC_REF_1);
rowValidation("NC_CIRC_REF_2", RESULT_CIRC_REF_2);
editColumnByName(
"NC_CIRC_REF_0",
"NC_CIRC_REF_0",
"CONCAT({City},{City})"
);
rowValidation("NC_CIRC_REF_0", RESULT_CIRC_REF_0_FINAL);
rowValidation("NC_CIRC_REF_2", RESULT_CIRC_REF_2_FINAL);
deleteColumnByName("NC_CIRC_REF_2");
deleteColumnByName("NC_CIRC_REF_1");
deleteColumnByName("NC_CIRC_REF_0");
cy.closeTableTab("City");
});
});
};

110
scripts/cypress/integration/common/3c_lookup_column.js

@ -1,110 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - LookUp column`, () => {
// to retrieve few v-input nodes from their label
//
const fetchParentFromLabel = (label) => {
cy.get("label").contains(label).parents(".ant-row").click();
};
// before(() => {
// });
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
// after(() => {
// cy.closeTableTab("City");
// });
// Routine to create a new look up column
//
const addLookUpColumn = (childTable, childCol) => {
cy.get(".nc-grid tr > th:last .nc-icon").click({
force: true,
});
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find("input.nc-column-name-input")
.should("exist")
.clear()
.type(childCol);
// cy.get(".nc-column-type-input").last().click().type("Lookup");
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".nc-column-type-input")
.last()
.click()
.type("Lookup");
cy.getActiveSelection(".nc-dropdown-column-type")
.find(".ant-select-item-option")
.contains("Lookup")
.click();
cy.inputHighlightRenderWait();
// Configure Child table & column names
fetchParentFromLabel("Child table");
cy.getActiveSelection(".nc-dropdown-relation-table")
.find(".ant-select-item-option")
.contains(childTable)
.click();
fetchParentFromLabel("Child column");
cy.getActiveSelection(".nc-dropdown-relation-column")
.find(".ant-select-item-option")
.contains(childCol)
.click();
// cy.get(".ant-btn-primary").contains("Save").should('exist').click();
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".ant-btn-primary:visible")
.contains("Save")
.click();
cy.toastWait(`Column created`);
cy.get(`th[data-title="${childCol}"]`).should("exist");
};
// routine to delete column
//
const deleteColumnByName = (childCol) => {
mainPage.deleteColumn(childCol);
};
///////////////////////////////////////////////////
// Test case
it("Add Lookup column (Address, PostalCode) & Delete", () => {
cy.openTableTab("City", 25);
addLookUpColumn("Address", "PostalCode");
// Verify first entry, will be displayed as alias here 'childColumn (from childTable)'
mainPage.getCell("PostalCode", 1).contains("4166").should("exist");
deleteColumnByName("PostalCode");
cy.closeTableTab("City");
});
it.skip("Add Lookup column (Country, CountryId) & Delete", () => {
addLookUpColumn("Country", "CountryId");
// Verify first entry, will be displayed as alias here 'childColumn (from childTable)'
cy.get(`tbody > :nth-child(1) > [data-col="CountryId"]`)
.contains("87")
.should("exist");
deleteColumnByName("CountryId");
});
});
};

147
scripts/cypress/integration/common/3d_rollup_column.js

@ -1,147 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - RollUp column`, () => {
// to retrieve few v-input nodes from their label
//
const fetchParentFromLabel = (label) => {
cy.get("label").contains(label).parents(".ant-row").click();
};
// before(() => {
// });
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
// after(() => {
// cy.closeTableTab("Country");
// });
// Routine to create a new look up column
//
const addRollUpColumn = (
columnName,
childTable,
childCol,
aggregateFunc
) => {
cy.get(".nc-grid tr > th:last .nc-icon").click({
force: true,
});
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find("input.nc-column-name-input")
.should("exist")
.clear()
.type(columnName);
// cy.get(".nc-column-type-input").last().click().type("RollUp");
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".nc-column-type-input")
.last()
.click()
.type("RollUp");
cy.getActiveSelection(".nc-dropdown-column-type")
.find(".ant-select-item-option")
.contains("Rollup")
.click();
cy.inputHighlightRenderWait();
// Configure Child table & column names
fetchParentFromLabel("Child table");
cy.getActiveSelection(".nc-dropdown-relation-table")
.find(".ant-select-item-option")
.contains(childTable)
.click();
fetchParentFromLabel("Child column");
cy.getActiveSelection(".nc-dropdown-relation-column")
.find(".ant-select-item-option")
.contains(childCol)
.click();
fetchParentFromLabel("Aggregate function");
cy.getActiveSelection(".nc-dropdown-rollup-function")
.find(".ant-select-item-option")
.contains(aggregateFunc)
.click();
// cy.get(".ant-btn-primary").contains("Save").should('exist').click();
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".ant-btn-primary:visible")
.contains("Save")
.click();
cy.toastWait(`Column created`);
cy.get(`th[data-title="${columnName}"]`).should("exist");
};
// routine to delete column
//
const deleteColumnByName = (columnName) => {
mainPage.deleteColumn(columnName);
};
// routine to edit column
//
// const editColumnByName = (oldName, newName) => {
// // verify if column exists before delete
// cy.get(`th:contains(${oldName})`).should("exist");
//
// // delete opiton visible on mouse-over
// cy.get(`th:contains(${oldName}) .mdi-menu-down`)
// .trigger("mouseover")
// .click();
//
// // edit/ save on pop-up
// cy.get(".nc-column-edit").click();
// cy.get(".nc-column-name-input input").clear().type(newName);
// cy.get(".nc-col-create-or-edit-card").contains("Save").click();
//
// cy.toastWait("Successfully updated alias");
//
// // validate if deleted (column shouldnt exist)
// cy.get(`th:contains(${oldName})`).should("not.exist");
// cy.get(`th:contains(${newName})`).should("exist");
// };
///////////////////////////////////////////////////
// Test case
it("Add Rollup column (City, City, count) & Delete", () => {
cy.openTableTab("Country", 25);
addRollUpColumn("RollUpCol", "City", "City", "count");
// Verify first entry, will be displayed as alias here 'childColumn (from childTable)'
// intentionally verifying 4th item, as initial items are being masked out by list scroll down
mainPage.getCell("RollUpCol", 4).contains("2").should("exist");
// editColumnByName("RollUpCol_2", "RollUpCol_New");
deleteColumnByName("RollUpCol");
cy.closeTableTab("Country");
});
it.skip("Add Rollup column (City, CountryId, count) & Delete", () => {
addRollUpColumn("RollUpCol_1", "City", "CountryId", "count");
// Verify first entry, will be displayed as alias here 'childColumn (from childTable)'
cy.get(`tbody > :nth-child(4) > [data-col="RollUpCol_1"]`)
.contains("2")
.should("exist");
// editColumnByName("RollUpCol_1", "RollUpCol_New");
deleteColumnByName("RollUpCol_New");
});
});
};

296
scripts/cypress/integration/common/3e_duration_column.js

@ -1,296 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - DURATION`, () => {
const tableName = "DurationTable";
// to retrieve few v-input nodes from their label
//
const fetchParentFromLabel = (label) => {
cy.get("label").contains(label).parents(".ant-row").first().click();
};
before(() => {
cy.restoreLocalStorage();
cy.createTable(tableName);
cy.saveLocalStorage();
});
// Run once before test- create table
//
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
after(() => {
cy.restoreLocalStorage();
cy.deleteTable(tableName);
cy.saveLocalStorage();
});
// Routine to create a new look up column
//
const addDurationColumn = (columnName, durationFormat) => {
cy.get(".nc-grid tr > th:last .nc-icon").click({
force: true,
});
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find("input.nc-column-name-input", { timeout: 3000 })
.should("exist")
.clear()
.type(columnName);
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".nc-column-type-input")
.last()
.click()
.type("Duration");
cy.getActiveSelection(".nc-dropdown-column-type")
.find(".ant-select-item-option")
.contains("Duration")
.click();
cy.inputHighlightRenderWait();
// Configure Duration format
fetchParentFromLabel("Duration Format");
cy.getActiveSelection(".nc-dropdown-duration-option")
.find(".ant-select-item-option")
.contains(durationFormat)
.click();
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".ant-btn-primary:visible")
.contains("Save")
.click();
cy.toastWait(`Column created`);
cy.get(`th[data-title="${columnName}"]`).should("exist");
};
// routine to delete column
//
const deleteColumnByName = (columnName) => {
mainPage.deleteColumn(columnName);
};
// routine to edit column
//
const editColumnByName = (oldName, newName, newDurationFormat) => {
cy.get(`th:contains(${oldName}) .nc-icon.ant-dropdown-trigger`)
.trigger("mouseover", { force: true })
.click({ force: true });
cy.getActiveMenu(".nc-dropdown-column-operations")
.find(".nc-column-edit")
.click();
// rename column and verify
cy.getActiveMenu(".nc-dropdown-edit-column")
.find("input.nc-column-name-input", { timeout: 3000 })
.should("exist")
.clear()
.type(newName);
cy.inputHighlightRenderWait();
// Configure Duration format
fetchParentFromLabel("Duration Format");
cy.getActiveSelection(".nc-dropdown-duration-option")
.find(".ant-select-item-option")
.contains(newDurationFormat)
.click();
cy.getActiveMenu(".nc-dropdown-edit-column")
.find(".ant-btn-primary:visible")
.contains("Save")
.click();
cy.toastWait("Column updated");
cy.get(`th:contains(${oldName})`).should("not.exist");
cy.get(`th:contains(${newName})`).should("exist");
};
const addDurationData = (
colName,
index,
cellValue,
expectedValue,
isNewRow = false
) => {
if (isNewRow) {
mainPage.addNewRowExpand("DurationTable");
} else {
// mainPage.getRow(index).find(".nc-row-expand-icon").click({ force: true });
cy.get(".nc-row-expand")
.eq(index - 1)
.click({ force: true });
}
cy.get(".duration-cell-wrapper > input")
.first()
.should("exist")
.type(cellValue);
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find("button")
.contains("Save row")
.should("exist")
.click();
cy.toastWait("Row updated successfully");
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find("button")
.contains("Cancel")
.should("exist")
.click();
mainPage.getCell(colName, index).contains(expectedValue).should("exist");
};
///////////////////////////////////////////////////
// Test case
{
// Duration: h:mm
it("Duration: h:mm", () => {
addDurationColumn("NC_DURATION_0", "h:mm (e.g. 1:23)");
addDurationData("NC_DURATION_0", 1, "1:30", "01:30", true);
addDurationData("NC_DURATION_0", 2, "30", "00:30", true);
addDurationData("NC_DURATION_0", 3, "60", "01:00", true);
addDurationData("NC_DURATION_0", 4, "80", "01:20", true);
addDurationData("NC_DURATION_0", 5, "12:34", "12:34", true);
addDurationData("NC_DURATION_0", 6, "15:130", "17:10", true);
addDurationData("NC_DURATION_0", 7, "123123", "2052:03", true);
});
it("Duration: Edit Column NC_DURATION_0", () => {
editColumnByName(
"NC_DURATION_0",
"NC_DURATION_EDITED_0",
"h:mm:ss (e.g. 3:45, 1:23:40)"
);
});
it("Duration: Delete column", () => {
deleteColumnByName("NC_DURATION_EDITED_0");
});
}
{
// Duration: h:mm:ss
it("Duration: h:mm:ss", () => {
addDurationColumn("NC_DURATION_1", "h:mm:ss (e.g. 3:45, 1:23:40)");
addDurationData("NC_DURATION_1", 1, "11:22:33", "11:22:33");
addDurationData("NC_DURATION_1", 2, "1234", "00:20:34");
addDurationData("NC_DURATION_1", 3, "50", "00:00:50");
addDurationData("NC_DURATION_1", 4, "1:1111", "00:19:31");
addDurationData("NC_DURATION_1", 5, "1:11:1111", "01:29:31");
addDurationData("NC_DURATION_1", 6, "15:130", "00:17:10");
addDurationData("NC_DURATION_1", 7, "123123", "34:12:03");
});
it("Duration: Edit Column NC_DURATION_1", () => {
editColumnByName(
"NC_DURATION_1",
"NC_DURATION_EDITED_1",
"h:mm:ss.s (e.g. 3:34.6, 1:23:40.0)"
);
});
it("Duration: Delete column", () => {
deleteColumnByName("NC_DURATION_EDITED_1");
});
}
{
// h:mm:ss.s
it("Duration: h:mm:ss.s", () => {
addDurationColumn(
"NC_DURATION_2",
"h:mm:ss.s (e.g. 3:34.6, 1:23:40.0)"
);
addDurationData("NC_DURATION_2", 1, "1234", "00:20:34.0");
addDurationData("NC_DURATION_2", 2, "12:34", "00:12:34.0");
addDurationData("NC_DURATION_2", 3, "12:34:56", "12:34:56.0");
addDurationData("NC_DURATION_2", 4, "12:34:999", "12:50:39.0");
addDurationData("NC_DURATION_2", 5, "12:999:56", "28:39:56.0");
addDurationData("NC_DURATION_2", 6, "12:34:56.12", "12:34:56.1");
addDurationData("NC_DURATION_2", 7, "12:34:56.199", "12:34:56.2");
});
it("Duration: Edit Column NC_DURATION_2", () => {
editColumnByName(
"NC_DURATION_2",
"NC_DURATION_EDITED_2",
"h:mm:ss (e.g. 3:45, 1:23:40)"
);
});
it("Duration: Delete column", () => {
deleteColumnByName("NC_DURATION_EDITED_2");
});
}
{
// h:mm:ss.ss
it("Duration: h:mm:ss.ss", () => {
addDurationColumn(
"NC_DURATION_3",
"h:mm:ss.ss (e.g. 3.45.67, 1:23:40.00)"
);
addDurationData("NC_DURATION_3", 1, "1234", "00:20:34.00");
addDurationData("NC_DURATION_3", 2, "12:34", "00:12:34.00");
addDurationData("NC_DURATION_3", 3, "12:34:56", "12:34:56.00");
addDurationData("NC_DURATION_3", 4, "12:34:999", "12:50:39.00");
addDurationData("NC_DURATION_3", 5, "12:999:56", "28:39:56.00");
addDurationData("NC_DURATION_3", 6, "12:34:56.12", "12:34:56.12");
addDurationData("NC_DURATION_3", 7, "12:34:56.199", "12:34:56.20");
});
it("Duration: Edit Column NC_DURATION_3", () => {
editColumnByName(
"NC_DURATION_3",
"NC_DURATION_EDITED_3",
"h:mm:ss.ss (e.g. 3.45.67, 1:23:40.00)"
);
});
it("Duration: Delete column", () => {
deleteColumnByName("NC_DURATION_EDITED_3");
});
}
{
// h:mm:ss.sss
it("Duration: h:mm:ss.sss", () => {
addDurationColumn(
"NC_DURATION_4",
"h:mm:ss.sss (e.g. 3.45.678, 1:23:40.000)"
);
addDurationData("NC_DURATION_4", 1, "1234", "00:20:34.000");
addDurationData("NC_DURATION_4", 2, "12:34", "00:12:34.000");
addDurationData("NC_DURATION_4", 3, "12:34:56", "12:34:56.000");
addDurationData("NC_DURATION_4", 4, "12:34:999", "12:50:39.000");
addDurationData("NC_DURATION_4", 5, "12:999:56", "28:39:56.000");
addDurationData("NC_DURATION_4", 6, "12:34:56.12", "12:34:56.012");
addDurationData("NC_DURATION_4", 7, "12:34:56.199", "12:34:56.199");
});
it("Duration: Edit Column NC_DURATION_4", () => {
editColumnByName(
"NC_DURATION_4",
"NC_DURATION_EDITED_4",
"h:mm (e.g. 1:23)"
);
});
it("Duration: Delete column", () => {
deleteColumnByName("NC_DURATION_EDITED_4");
});
}
});
};

380
scripts/cypress/integration/common/3f_link_to_another_record.js

@ -1,380 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { loginPage } from "../../support/page_objects/navigation";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
// tbd: this needs a proper fix
let waitTime = 0;
let clear;
describe(`${apiType.toUpperCase()} api - Link to another record`, () => {
function fetchParentFromLabel(label) {
cy.get("label").contains(label).parents(".ant-row").click();
}
// Insert new row
function addRow(index, cellValue) {
cy.get(".nc-grid-add-new-cell").should("exist").click();
mainPage
.getCell("Title", index)
.dblclick()
.then(($el) => {
cy.wrap($el).find("input").clear().type(`${cellValue}{enter}`);
});
mainPage.getCell("Title", index).contains(cellValue).should("exist");
}
// Insert LTAR column
//
function addLtarColumn(columnName, foreignTable, relationType) {
// + icon
cy.get(".nc-grid tr > th:last .nc-icon").click();
// Column name
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find("input.nc-column-name-input", { timeout: 3000 })
.should("exist")
.clear()
.type(columnName);
// Column type
// cy.get(".nc-column-type-input").last()
// .click()
// .type("Link");
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".nc-column-type-input")
.last()
.click()
.type("Link");
cy.getActiveSelection(".nc-dropdown-column-type")
.find(".ant-select-item-option")
.contains("LinkToAnotherRecord")
.click();
// relation type (hm/ mm)
cy.get(".nc-ltar-relation-type")
.find(".ant-radio")
.eq(relationType === "hm" ? 0 : 1)
.click();
// Foreign table
fetchParentFromLabel("Child table");
cy.get(".nc-ltar-child-table").last().click().type(foreignTable);
cy.getActiveSelection(".nc-dropdown-ltar-child-table")
.find(".ant-select-item-option")
.contains(foreignTable)
.click();
// Save
// cy.get(".ant-btn-primary")
// .contains("Save")
// .should('exist')
// .click();
cy.getActiveMenu(".nc-dropdown-grid-add-column")
.find(".ant-btn-primary:visible")
.contains("Save")
.click();
// Toast
cy.toastWait(`Column created`);
// Verify
cy.get(`th[data-title="${columnName}"]`).should("exist");
}
// Content verification for LTAR cell
// Validates only 1st chip contents
//
function verifyLtarCell(columnName, index, cellValue) {
cy.get(`:nth-child(${index}) > [data-title="${columnName}"]`)
.find(".chip")
.eq(0)
.contains(cellValue)
.should("exist");
}
// Unlink LTAR cell
//
function ltarUnlink(columnName, index) {
// http://localhost:8080/api/v1/db/meta/audits/comments/count?ids[]=1&fk_model_id=md_f4y7jp89pe8vkt
cy.intercept("GET", `/api/v1/db/meta/audits/comments/count?**`).as(
"unlinkCount"
);
// Click on cell to enable unlink icon
cy.get(`:nth-child(${index}) > [data-title="${columnName}"]`)
.last()
.click();
// Click on unlink icon
cy.get(`:nth-child(${index}) > [data-title="${columnName}"]`)
.last()
.find(".unlink-icon")
.should("exist")
.click();
// Glitch; hence wait
cy.wait("@unlinkCount");
}
before(() => {
cy.restoreLocalStorage();
clear = Cypress.LocalStorage.clear;
Cypress.LocalStorage.clear = () => {};
});
afterEach(() => {
cy.saveLocalStorage();
});
beforeEach(() => {
cy.restoreLocalStorage();
});
after(() => {
// Cleanup
//
cy.restoreLocalStorage();
cy.openTableTab("Sheet1", 0);
mainPage.deleteColumn("Link1-2hm");
mainPage.deleteColumn("Link1-2mm");
mainPage.deleteColumn("Sheet2");
cy.deleteTable("Sheet1");
cy.deleteTable("Sheet2");
cy.saveLocalStorage();
Cypress.LocalStorage.clear = clear;
});
///////////////////////////////////////////////////
// Test case
it("Create Link columns", () => {
cy.createTable("Sheet1");
cy.createTable("Sheet2");
cy.openTableTab("Sheet1", 0);
addRow(1, "1a");
addRow(2, "1b");
addRow(3, "1c");
addLtarColumn("Link1-2hm", "Sheet2", "hm");
addLtarColumn("Link1-2mm", "Sheet2", "mm");
cy.closeTableTab("Sheet1");
cy.openTableTab("Sheet2", 0);
addLtarColumn("Link2-1hm", "Sheet1", "hm");
cy.closeTableTab("Sheet2");
// Sheet2 now has all 3 column categories : HM, BT, MM
//
});
// Expand form [Add new row]
//
it("Add HM, BT, MM Link, Expand form", () => {
// http://localhost:8080/api/v1/db/data/noco/p_0l53e1xgsxlecb/md_mn4xgb2jnq16a7?limit=10&offset=0&where=&fields[]=Title&fields[]=Id
cy.intercept("GET", `/api/v1/db/data/noco/**`).as("waitForCardLoad");
cy.openTableTab("Sheet2", 0);
// Click on `Add new row` button
mainPage.addNewRowExpand("Sheet2");
// Title
cy.get(".nc-expand-col-Title")
.find(".nc-cell > input")
.should("exist")
.first()
.clear()
.type("2a");
// trigger("mouseover") is required to show the + icon
// didn't seem to work. As a kludge, used click with {force:true}
// additional delay ensures card contents are available before clicking
//
// BT
cy.get(".nc-expand-col-Sheet1")
.find(".nc-action-icon")
.should("exist")
.click({ force: true });
cy.wait("@waitForCardLoad");
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.should("exist")
.eq(0)
.click();
// MM
cy.get(".nc-expand-col-Sheet1.List").find(".ant-btn-primary").click();
cy.wait("@waitForCardLoad");
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.should("exist")
.eq(0)
.click();
// HM
cy.get(".nc-expand-col-Link2-1hm").find(".ant-btn-primary").click();
cy.wait("@waitForCardLoad");
cy.getActiveModal().find(".ant-card").should("exist").eq(0).click();
// Save row
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find("button")
.contains("Save row")
.should("exist")
.click();
// Toast
cy.toastWait("updated successfully");
// Close modal
cy.get("body").type("{esc}");
});
// In cell insert
it("Add HM, BT, MM Link, In cell form", () => {
// Insert row with `Title` field, rest of links are empty
addRow(2, "2b");
// BT
mainPage
.getCell("Sheet1", 2)
.find(".nc-action-icon")
.click({ force: true });
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.should("exist")
.eq(1)
.click();
// MM
mainPage
.getCell("Sheet1 List", 2)
.find(".nc-action-icon")
.last()
.click({ force: true });
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.should("exist")
.eq(1)
.click();
// HM
mainPage
.getCell("Link2-1hm", 2)
.find(".nc-action-icon")
.last()
.click({ force: true });
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.should("exist")
.eq(1)
.click();
});
// Existing row, expand record
it("Add HM, BT, MM Link, expand record", () => {
// http://localhost:8080/api/v1/db/data/noco/p_0l53e1xgsxlecb/md_mn4xgb2jnq16a7?limit=10&offset=0&where=&fields[]=Title&fields[]=Id
cy.intercept("GET", `/api/v1/db/data/noco/**`).as("waitForCardLoad");
addRow(3, "2c");
// kludge; remove empty record in the end
mainPage.getCell("Title", 3).click();
mainPage.getCell("Title", 4).rightclick();
// delete row
cy.getActiveMenu(".nc-dropdown-grid-context-menu")
.find('.ant-dropdown-menu-item:contains("Delete Row")')
.first()
.click();
cy.get(".nc-row-expand").eq(2).click({ force: true });
// wait for page render to complete
cy.get('button:contains("Save row"):visible').should("exist");
// BT
cy.get(".nc-expand-col-Sheet1")
.find(".nc-action-icon")
.should("exist")
.click({ force: true });
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.should("exist")
.eq(2)
.click();
// MM
cy.get(".nc-expand-col-Sheet1.List").find(".ant-btn-primary").click();
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.should("exist")
.eq(2)
.click();
// HM
cy.get(".nc-expand-col-Link2-1hm").find(".ant-btn-primary").click();
cy.getActiveModal(".nc-modal-link-record")
.find(".ant-card")
.should("exist")
.eq(2)
.click();
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find("button")
.contains("Save row")
.should("exist")
.click();
// cy.toastWait("updated successfully");
cy.toastWait("No columns to update");
cy.get("body").type("{esc}");
verifyLtarCell("Sheet1", 1, "1a");
verifyLtarCell("Sheet1", 2, "1b");
verifyLtarCell("Sheet1", 3, "1c");
verifyLtarCell("Sheet1 List", 1, "1a");
verifyLtarCell("Sheet1 List", 2, "1b");
verifyLtarCell("Sheet1 List", 3, "1c");
verifyLtarCell("Link2-1hm", 1, "1a");
verifyLtarCell("Link2-1hm", 2, "1b");
verifyLtarCell("Link2-1hm", 3, "1c");
cy.closeTableTab("Sheet2");
});
it("Verification", () => {
cy.openTableTab("Sheet1", 3);
verifyLtarCell("Link1-2hm", 1, "2a");
verifyLtarCell("Link1-2hm", 2, "2b");
verifyLtarCell("Link1-2hm", 3, "2c");
verifyLtarCell("Link1-2mm", 1, "2a");
verifyLtarCell("Link1-2mm", 2, "2b");
verifyLtarCell("Link1-2mm", 3, "2c");
verifyLtarCell("Sheet2", 1, "2a");
verifyLtarCell("Sheet2", 2, "2b");
verifyLtarCell("Sheet2", 3, "2c");
cy.closeTableTab("Sheet1");
});
it("Unlink", () => {
cy.openTableTab("Sheet1", 3);
ltarUnlink("Link1-2hm", 1);
ltarUnlink("Link1-2hm", 2);
ltarUnlink("Link1-2hm", 3);
ltarUnlink("Link1-2mm", 1);
ltarUnlink("Link1-2mm", 2);
ltarUnlink("Link1-2mm", 3);
ltarUnlink("Sheet2", 1);
ltarUnlink("Sheet2", 2);
ltarUnlink("Sheet2", 3);
cy.closeTableTab("Sheet1");
});
});
};

95
scripts/cypress/integration/common/4a_table_view_grid_gallery_form.js

@ -1,95 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - Table views: Create/Edit/Delete`, () => {
const name = "Test" + Date.now();
// Run once before test- create project (rest/graphql)
//
before(() => {
cy.restoreLocalStorage();
// open a table to work on views
//
cy.openTableTab("Country", 25);
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
after(() => {
cy.restoreLocalStorage();
cy.closeTableTab("Country");
cy.saveLocalStorage();
});
// Common routine to create/edit/delete GRID & GALLERY view
// Input: viewType - 'grid'/'gallery'
//
const viewTest = (viewType) => {
it(`Create ${viewType} view`, () => {
// click on 'Grid/Gallery' button on Views bar
cy.get(`.nc-create-${viewType}-view`).click();
// Pop up window, click Submit (accepting default name for view)
cy.getActiveModal(".nc-modal-view-create")
.find(".ant-btn-primary")
.click();
cy.toastWait("View created successfully");
// validate if view was created && contains default name 'Country1'
cy.get(`.nc-${viewType}-view-item`)
.contains(`${capitalizeFirstLetter(viewType)}-1`)
.should("exist");
});
it(`Edit ${viewType} view name`, () => {
// click on edit-icon (becomes visible on hovering mouse)
cy.get(`.nc-${viewType}-view-item`).last().dblclick();
// feed new name
cy.get(`.nc-${viewType}-view-item input`)
.clear()
.type(`${viewType}View-1{enter}`);
cy.toastWait("View renamed successfully");
// validate
cy.get(`.nc-${viewType}-view-item`)
.contains(`${viewType}View-1`)
.should("exist");
});
it(`Delete ${viewType} view`, () => {
// number of view entries should be 2 before we delete
cy.get(".nc-view-item").its("length").should("eq", 2);
// click on delete icon (becomes visible on hovering mouse)
cy.get(".nc-view-delete-icon").click({ force: true });
cy.getActiveModal(".nc-modal-view-delete")
.find(".ant-btn-dangerous")
.click();
cy.toastWait("View deleted successfully");
// confirm if the number of veiw entries is reduced by 1
cy.get(".nc-view-item").its("length").should("eq", 1);
});
};
// below four scenario's will be invoked twice, once for rest & then for graphql
viewTest("grid"); // grid view
viewTest("gallery"); // gallery view
viewTest("form"); // form view
});
};

120
scripts/cypress/integration/common/4b_table_view_share.js

@ -1,120 +0,0 @@
import { mainPage } from "../../support/page_objects/mainPage";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
import { loginPage } from "../../support/page_objects/navigation";
let storedURL = "";
let linkText = "";
const generateLinkWithPwd = () => {
mainPage.shareView().click();
cy.getActiveModal(".nc-modal-share-view")
.find(".ant-modal-title")
.contains("This view is shared via a private link")
.should("be.visible");
// enable checkbox & feed pwd, save
cy.get('[data-cy="nc-modal-share-view__with-password"]').click();
cy.get('[data-cy="nc-modal-share-view__password"]').clear().type('1')
cy.get('[data-cy="nc-modal-share-view__save-password"]').click();
cy.toastWait("Successfully updated");
// copy link text, visit URL
cy.get('[data-cy="nc-modal-share-view__link"]').then(($el) => {
linkText = $el.text();
// todo: visit url?
cy.log(linkText);
})
};
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - Shared VIEWs (GRID)`, () => {
// Run once before test- create project (rest/graphql)
//
before(() => {
cy.restoreLocalStorage();
cy.openTableTab("City", 25);
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
after(() => {
cy.restoreLocalStorage();
cy.closeTableTab("City");
cy.saveLocalStorage();
});
it("Generate link with password", () => {
// store base URL- to re-visit and delete form view later
cy.url().then((url) => {
storedURL = url;
});
generateLinkWithPwd();
cy.signOut();
});
it("Share view with incorrect password", () => {
cy.visit(linkText, {
baseUrl: null,
});
cy.getActiveModal(".nc-modal-shared-view-password-dlg").should("exist");
// feed password
cy.getActiveModal(".nc-modal-shared-view-password-dlg")
.find('input[type="password"]')
.clear()
.type("a");
cy.getActiveModal(".nc-modal-shared-view-password-dlg")
.find('button:contains("Unlock")')
.click();
// if pwd is incorrect, active modal requesting to feed in password again will persist
cy.getActiveModal(".nc-modal-shared-view-password-dlg")
.find('button:contains("Unlock")')
.should("exist");
});
// fallover test- use previously opened view & continue verification instead of opening again
it("Share view with correct password", () => {
// feed password
cy.getActiveModal(".nc-modal-shared-view-password-dlg")
.find('input[type="password"]')
.clear()
.type("1");
cy.getActiveModal(".nc-modal-shared-view-password-dlg")
.find('button:contains("Unlock")')
.click();
// if pwd is incorrect, active modal requesting to feed in password again will persist
// cy.getActiveModal().find('button:contains("Unlock")').should('not.exist');
// cy.get(".ant-modal-content:visible").should("not.exist")
cy.wait(1000);
// Verify Download as CSV is here
mainPage.downloadCsv().should("exist");
cy.get(".nc-actions-menu-btn").should("exist").click();
mainPage.downloadExcel().should("exist");
cy.get(".nc-actions-menu-btn").should("exist").click();
});
it("Delete view", () => {
loginPage.loginAndOpenProject(apiType, dbType);
cy.openTableTab("City", 25);
// wait for page load to complete
cy.get(".nc-grid-row").should("have.length", 25);
mainPage.deleteCreatedViews();
});
});
};

406
scripts/cypress/integration/common/4c_form_view_detailed.js

@ -1,406 +0,0 @@
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
import { mainPage, settingsPage } from "../../support/page_objects/mainPage";
import { loginPage } from "../../support/page_objects/navigation";
let formViewURL;
function verifyFormDrawerFieldLocation(fieldName, position) {
cy.get(".nc-editable.item").eq(position).contains(fieldName).should("exist");
}
function verifyFormDrawerHideObjectCount(count) {
if (count) {
cy.get(".nc-form")
.find(".nc-field-remove-icon")
.its("length")
.should("eq", count);
} else {
cy.get(".nc-form").find(".nc-field-remove-icon").should("not.exist");
}
}
function verifyFormMenuDrawerCardCount(cardCount) {
if (cardCount) {
cy.get(".nc-form-left-drawer")
.find(".ant-card")
.should("have.length", cardCount);
} else {
cy.get(".nc-form-left-drawer").find(".ant-card").should("not.exist");
}
}
function validateFormHeader() {
cy.get(".nc-form").should("exist");
cy.get(".nc-form")
.find('[placeholder="Form Title"]')
.should("exist")
.then(($el) => {
cy.log($el);
expect($el.val()).to.equal("A B C D");
});
cy.get(".nc-form")
.find('[placeholder="Add form description"]')
.should("exist")
.then(($el) => {
cy.log($el);
expect($el.val()).to.equal("Some description about form comes here");
});
}
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - FORM view`, () => {
const name = "Test" + Date.now();
// Run once before test- create project (rest/graphql)
//
before(() => {
// standalone test
// loginPage.loginAndOpenProject(apiType, dbType);
// open a table to work on views
//
cy.restoreLocalStorage();
cy.openTableTab("Country", 25);
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
after(() => {
cy.restoreLocalStorage();
cy.closeTableTab("Country");
cy.saveLocalStorage();
});
// Common routine to create/edit/delete GRID & GALLERY view
// Input: viewType - 'grid'/'gallery'
//
const viewTest = (viewType) => {
it(`Create ${viewType} view`, () => {
// click on 'Grid/Gallery' button on Views bar
cy.get(`.nc-create-${viewType}-view`).click();
// Pop up window, click Submit (accepting default name for view)
cy.getActiveModal(".nc-modal-view-create")
.find("button:contains(Submit)")
.click();
cy.toastWait("View created successfully");
// validate if view was creted && contains default name 'Form-1'
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1")
.should("exist");
});
it(`Validate ${viewType} view: Drag & drop for re-order items`, () => {
// default order: Country, LastUpdate, Country => City
verifyFormDrawerFieldLocation("Country", 0);
verifyFormDrawerFieldLocation("LastUpdate", 1);
// move Country field down (drag, drop)
cy.get(".nc-form-drag-LastUpdate").drag(".nc-form-drag-Country");
cy.wait(1000);
// Verify if order is: LastUpdate, Country, Country => City
verifyFormDrawerFieldLocation("LastUpdate", 0);
verifyFormDrawerFieldLocation("Country", 1);
});
it(`Validate ${viewType} view: Drag & drop for add/remove items`, () => {
// default, only one item in menu-bar; ensure LastUpdate field was present in form view
verifyFormMenuDrawerCardCount(0);
verifyFormDrawerFieldLocation("LastUpdate", 0);
// drag 'LastUpdate' & drop into menu bar drag-drop box
cy.get(".nc-form-drag-LastUpdate").drag(".nc-drag-n-drop-to-hide");
// validate- fields count in menu bar to be increased by 1 &&
// first member in 'formView' is Country
verifyFormDrawerFieldLocation("Country", 0);
verifyFormMenuDrawerCardCount(1);
});
it(`Validate ${viewType} view: Inverted order field member addition from menu`, () => {
cy.get(".nc-form-remove-all").click();
verifyFormMenuDrawerCardCount(2);
// click fields in inverted order: LastUpdate, Country => City
cy.get(".nc-form-left-drawer").find(".ant-card").eq(1).click();
verifyFormMenuDrawerCardCount(1);
cy.get(".nc-form-left-drawer").find(".ant-card").eq(0).click();
// verify if order of appearance in form is right
// Country was never removed as its required field. Other two will appear in inverted order
verifyFormMenuDrawerCardCount(0);
verifyFormDrawerFieldLocation("Country", 0);
verifyFormDrawerFieldLocation("City List", 1);
verifyFormDrawerFieldLocation("LastUpdate", 2);
});
it(`Validate ${viewType}: Form header & description validation`, () => {
// Header & description should exist
cy.get(".nc-form").find('[placeholder="Form Title"]').should("exist");
cy.get(".nc-form")
.find('[placeholder="Add form description"]')
.should("exist");
// Update header & add some description, verify
cy.get(".nc-form")
.find('[placeholder="Form Title"]')
.clear()
.type("A B C D");
cy.get(".nc-form")
.find('[placeholder="Add form description"]')
.type("Some description about form comes here");
cy.get(".nc-form").click();
// validate new contents
validateFormHeader();
});
it(`Validate ${viewType}: Add all, Remove all validation`, () => {
// ensure buttons exist on left hand menu
cy.get(".nc-form-left-drawer")
.find(".nc-form-add-all")
.should("not.exist");
cy.get(".nc-form-left-drawer")
.find(".nc-form-remove-all")
.should("be.visible");
// click: remove-all
cy.get(".nc-form-left-drawer").find(".nc-form-remove-all").click();
cy.wait(1000);
// form should not contain any "field remove icons"
verifyFormDrawerHideObjectCount(0);
// menu bar should contain 2 .pointer.item (LastUpdate, County->City)
verifyFormMenuDrawerCardCount(2);
// click: Add all
cy.get(".nc-form-left-drawer")
.find(".nc-form-add-all")
.should("be.visible")
.click();
cy.get(".nc-form-left-drawer")
.find(".nc-form-remove-all")
.should("be.visible");
// form should contain "field remove icons"
verifyFormDrawerHideObjectCount(2);
// menu bar should not contain .pointer.item (column name/ field name add options)
verifyFormMenuDrawerCardCount(0);
});
it(`Validate ${viewType}: Submit default, empty show this message textbox`, () => {
// fill up mandatory fields
cy.get(".nc-form-input-Country").type("_abc");
cy.get(".nc-form-input-LastUpdate").click();
cy.get(".ant-picker-now-btn:visible").contains("Now").click();
cy.get(".ant-btn-primary:visible").contains("Ok").click();
// default message, no update
// submit button & validate
cy.get(".nc-form").find("button").contains("Submit").click();
cy.get(
".ant-alert-message :contains('Successfully submitted form data')"
).should("exist");
// end of test removes newly added rows from table. that step validates if row was successfully added.
});
it(`Validate ${viewType}: Submit default, with valid Show message entry`, () => {
// clicking again on view name shows blank still. work around- toggling between two views
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1")
.click();
// fill up mandatory fields
cy.get(".nc-form-input-Country").should("exist").type("_abc");
cy.get(".nc-form-input-LastUpdate").click();
cy.get(".ant-picker-now-btn:visible").contains("Now").click();
cy.get(".ant-btn-primary:visible").contains("Ok").click();
// add message
cy.get("textarea.nc-form-after-submit-msg").type("Congratulations!");
// submit button & validate
cy.get(".nc-form").find("button").contains("Submit").click();
cy.get(".ant-alert-message :contains('Congratulations!')").should(
"exist"
);
// end of test removes newly added rows from table. that step validates if row was successfully added.
});
it(`Validate ${viewType}: Submit default, Enable checkbox "Submit another form`, () => {
// clicking again on view name shows blank still. work around- toggling between two views
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1")
.click();
// fill up mandatory fields
cy.get(".nc-form-input-Country").type("_abc");
cy.get(".nc-form-input-LastUpdate").click();
cy.get(".ant-picker-now-btn:visible").contains("Now").click();
cy.get(".ant-btn-primary:visible").contains("Ok").click();
// enable "Submit another form" check box
cy.get("button.nc-form-checkbox-submit-another-form").click();
// submit button & validate
cy.get(".nc-form").find("button").contains("Submit").click();
cy.get(".ant-alert-message :contains('Congratulations!')").should(
"exist"
);
cy.get("button")
.contains("Submit Another Form")
.should("exist")
.click();
// New form appeared? Header & description should exist
validateFormHeader();
// end of test removes newly added rows from table. that step validates if row was successfully added.
});
it(`Validate ${viewType}: Submit default, Enable checkbox "blank form after 5 seconds"`, () => {
cy.get(".nc-form-input-Country").type("_abc");
cy.get(".nc-form-input-LastUpdate").click();
cy.get(".ant-picker-now-btn:visible").contains("Now").click();
cy.get(".ant-btn-primary:visible").contains("Ok").click();
// enable "New form after 5 seconds" button
cy.get("button.nc-form-checkbox-submit-another-form").click();
cy.get("button.nc-form-checkbox-show-blank-form").click();
// submit button & validate
cy.get(".nc-form").find("button").contains("Submit").click();
cy.get(".ant-alert-message :contains('Congratulations!')")
.should("exist")
.then(() => {
// validate if form has appeared again
validateFormHeader();
});
// end of test removes newly added rows from table. that step validates if row was successfully added.
});
it(`Validate ${viewType}: Email me verification, without SMTP configuration`, () => {
// open formview & enable "email me" option
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1")
.click();
// validate if form has appeared again
cy.wait(1000);
validateFormHeader();
cy.get(".nc-form-remove-all").click();
cy.get(".nc-form-checkbox-send-email").click();
// validate if toaster pops up requesting to activate SMTP
cy.toastWait(
"Please activate SMTP plugin in App store for enabling email notification"
);
});
it(`Validate ${viewType}: Email me verification, with SMTP configuration`, () => {
// activate SMTP, dummy profile
settingsPage.openMenu(settingsPage.APPSTORE);
mainPage.configureSMTP("admin@ex.com", "smtp.ex.com", "8080", "TLS");
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1")
.click();
// validate if form has appeared again
validateFormHeader();
cy.get(".nc-form-checkbox-send-email").click();
settingsPage.openMenu(settingsPage.APPSTORE);
mainPage.resetSMTP();
});
it(`Validate ${viewType}: Add/ remove field verification"`, () => {
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1")
.click();
cy.get(".nc-form-add-all").click();
cy.wait(300);
// validate if form has appeared again
validateFormHeader();
cy.get(".nc-form-input-LastUpdate").should("exist");
// remove "LastUpdate field"
cy.get(".nc-form").find(".nc-field-remove-icon").eq(1).click();
cy.get(".nc-form-input-LastUpdate").should("not.exist");
cy.get(".nc-form-left-drawer")
.find(".ant-card")
.contains("LastUpdate")
.should("exist")
.click();
cy.get(".nc-form-input-LastUpdate").should("exist");
cy.wait(300);
});
it(`Validate ${viewType}: URL verification`, () => {
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1")
.click();
// validate if form has appeared again
validateFormHeader();
});
it(`Delete ${viewType} view`, () => {
// number of view entries should be 2 before we delete
cy.get(".nc-view-item").its("length").should("eq", 2);
// click on delete icon (becomes visible on hovering mouse)
cy.get(".nc-view-delete-icon").click({ force: true });
cy.getActiveModal(".nc-modal-view-delete")
.find(".ant-btn-dangerous")
.click();
cy.toastWait("View deleted successfully");
// confirm if the number of veiw entries is reduced by 1
cy.get(".nc-view-item").its("length").should("eq", 1);
// clean up newly added rows into Country table operations
// this auto verifies successfull addition of rows to table as well
mainPage.getPagination(5).click();
cy.get(".nc-grid-row").should("have.length", 13);
cy.get(".ant-checkbox").should("exist").eq(10).click({ force: true });
cy.get(".ant-checkbox").should("exist").eq(11).click({ force: true });
cy.get(".ant-checkbox").should("exist").eq(12).click({ force: true });
cy.get(".ant-checkbox").should("exist").eq(13).click({ force: true });
mainPage.getCell("Country", 10).rightclick({ force: true });
cy.getActiveMenu(".nc-dropdown-grid-context-menu")
.contains("Delete Selected Rows")
.click({ force: true });
});
};
// below scenario's will be invoked twice, once for rest & then for graphql
viewTest("form");
});
};

107
scripts/cypress/integration/common/4d_table_view_grid_locked.js

@ -1,107 +0,0 @@
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
import { mainPage } from "../../support/page_objects/mainPage";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - Lock view`, () => {
// Run once before test- create project (rest/graphql)
//
before(() => {
cy.restoreLocalStorage();
cy.openTableTab("Country", 25);
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
after(() => {
cy.restoreLocalStorage();
cy.closeTableTab("Country");
cy.saveLocalStorage();
});
const lockViewTest = (enabled) => {
it(`Grid: lock view set to ${enabled}: validation`, () => {
let vString = enabled ? "not." : "";
let menuOption = enabled ? "Locked View" : "Collaborative View";
// on menu, collaboration view appears first (at index 0)
// followed by Locked view (at index 1)
cy.get(".nc-actions-menu-btn").click();
cy.getActiveMenu(".nc-dropdown-actions-menu")
.find(".ant-dropdown-menu-submenu")
.eq(0)
.click();
cy.wait(1000);
cy.get(".nc-locked-menu-item")
.contains(menuOption)
.should("exist")
.click();
// cy.get(".nc-sidebar-lock-menu")
// .click();
// cy.getActiveMenu()
// .find('.nc-menu-item:visible')
// .eq(menuOption)
// .click();
if (enabled) {
cy.toastWait("Successfully Switched to locked view");
cy.get(".nc-icon-locked").should("exist");
} else {
cy.toastWait("Successfully Switched to collaborative view");
cy.get(".nc-icon-collaborative").should("exist");
}
// expected toolbar for Lock view: Only lock-view menu, reload, toggle-nav-drawer to be enabled
//
// cy.get(".nc-sidebar-lock-menu:enabled")
// .should("exist");
cy.get(".nc-toolbar-reload-btn").should("exist");
cy.get(".nc-add-new-row-btn > .cursor-pointer").should(
`${vString}exist`
);
cy.get(".nc-fields-menu-btn:enabled").should(`${vString}exist`);
cy.get(".nc-sort-menu-btn:enabled").should(`${vString}exist`);
cy.get(".nc-filter-menu-btn:enabled").should(`${vString}exist`);
// dblClick on a cell & see if we can edit
mainPage.getCell("Country", 1).dblclick();
mainPage.getCell("Country", 1).find("input").should(`${vString}exist`);
// the expand button should be always enabled
cy.get(".nc-row-expand").should("exist");
// check if add/ expand options available for 'has many' column type
// GUI-v2: TBD
mainPage
.getCell("City List", 1)
.click()
.find(".nc-action-icon.nc-plus")
.should(`${vString}exist`);
mainPage
.getCell("City List", 1)
.click()
.find(".nc-action-icon.nc-arrow-expand")
.should(`${vString}exist`);
// update row option (right click) - should not be available for Lock view
mainPage.getCell("City List", 1).rightclick();
cy.get(".ant-dropdown-content").should(`${vString}be.visible`);
});
};
// Locked view
lockViewTest(true);
// collaboration view
lockViewTest(false);
});
};

226
scripts/cypress/integration/common/4e_form_view_share.js

@ -1,226 +0,0 @@
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
import { mainPage } from "../../support/page_objects/mainPage";
import { loginPage } from "../../support/page_objects/navigation";
let storedURL = "";
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
describe(`${apiType.toUpperCase()} api - FORM view (Share)`, () => {
const name = "Test" + Date.now();
// Run once before test- create project (rest/graphql)
//
before(() => {
// loginPage.loginAndOpenProject(apiType, dbType);
cy.restoreLocalStorage();
cy.openTableTab("City", 25);
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
after(() => {
cy.restoreLocalStorage();
cy.closeTableTab("City");
cy.saveLocalStorage();
});
// Common routine to create/edit/delete GRID & GALLERY view
// Input: viewType - 'grid'/'gallery'
//
const viewTest = (viewType) => {
it(`Create ${viewType} view`, () => {
0;
// click on create grid view button
cy.get(`.nc-create-${viewType}-view`).click();
// Pop up window, click Submit (accepting default name for view)
cy.getActiveModal(".nc-modal-view-create")
.find("button:contains(Submit)")
.click();
cy.toastWait("View created successfully");
// validate if view was creted && contains default name 'Country1'
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1")
.should("exist");
// Prepare form
// add header, description
// add post submission message
// swap position for City, LastUpdate fields
// remove City=>Address field
// enable "Submit another form" check box
cy.get("button.nc-form-checkbox-show-blank-form").click();
// [kludge] CI-CD: title is being rendered initially in disabled state
cy.wait(2000);
// Update header & add some description, verify
cy.get(".nc-form")
.find('[placeholder="Form Title"]')
.clear()
.type("A B C D");
cy.get(".nc-form")
.find('[placeholder="Add form description"]')
.type("Some description about form comes here");
// add message
cy.get("textarea.nc-form-after-submit-msg").type("Congratulations!");
// move Country field down (drag, drop)
cy.get(".nc-form-drag-LastUpdate").drag(".nc-form-drag-City");
cy.get('[title="Address List"]').drag(".nc-drag-n-drop-to-hide");
// store base URL- to re-visit and delete form view later
cy.url().then((url) => {
storedURL = url;
});
});
it(`Share form view`, () => {
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1")
.click();
cy.wait(2000);
mainPage.shareView().click();
// copy link text, visit URL
cy.getActiveModal(".nc-modal-share-view")
.should("exist")
.find(".share-link-box")
.contains("/nc/form/", { timeout: 10000 })
.should("exist")
.then(($obj) => {
// http://localhost:8080/api/v1/db/public/shared-view/761f0200-e72c-487a-85bf-615d0d277054/rows?offset=0&filterArrJson=[]&sortArrJson=[]
cy.intercept("/api/v1/db/public/shared-view/**").as(
"waitForPageLoad"
);
let linkText = $obj.text().trim();
cy.log(linkText);
cy.signOut();
cy.visit(linkText, {
baseUrl: null,
});
cy.wait(["@waitForPageLoad"], { times: 2 });
// wait for share view page to load!
cy.get(".nc-form").should("exist");
// New form appeared? Header & description should exist
cy.get(".nc-form-view", { timeout: 10000 })
.find("h1")
.contains("A B C D")
.should("exist");
cy.get(".nc-form-view", { timeout: 10000 })
.find("h2")
.contains("Some description about form comes here")
.should("exist");
// all fields, barring removed field should exist
cy.get('[title="City"]').should("exist");
cy.get('[title="LastUpdate"]').should("exist");
cy.get('[title="Country"]').should("exist");
cy.get('[title="Address List"]').should("not.exist");
// order of LastUpdate & City field is retained
cy.get(".nc-form-column-label")
.eq(0)
.contains("LastUpdate")
.should("exist");
cy.get(".nc-form-column-label")
.eq(1)
.contains("City")
.should("exist");
// submit form, to read message
cy.get(".nc-form-input-City").type("_abc");
cy.get(".nc-form-input-LastUpdate").click();
cy.get(".ant-picker-now-btn:visible").contains("Now").click();
cy.get(".ant-btn-primary:visible").contains("Ok").click();
// cy.get('.nc-form-field-Country')
// .trigger('mouseover')
// .click()
// .find('.nc-action-icon')
// .click();
// // cy.get("button").contains("Link to 'Country'").click();
// cy.getActiveModal()
// .find(".ant-card")
// .contains("Afghanistan")
// .click();
//
// // submit button & validate
// cy.get(".nc-form")
// .find("button")
// .contains("Submit")
// .click();
//
// cy.get(".ant-alert-message")
// .contains("Congratulations")
// .should("exist")
// .then(() => {
// cy.get(".nc-form").should("exist");
//
// // validate if form has appeared again
// cy.get(".nc-share-form-title")
// .contains("A B C D")
// .should("exist");
// cy.get(".nc-share-form-desc")
// .contains("Some description about form comes here")
// .should("exist");
// });
});
});
it(`Delete ${viewType} view`, () => {
// go back to base page
loginPage.loginAndOpenProject(apiType, dbType);
cy.openTableTab("City", 25);
// number of view entries should be 2 before we delete
cy.get(".nc-view-item").its("length").should("eq", 2);
// click on delete icon (becomes visible on hovering mouse)
cy.get(".nc-view-delete-icon").click({ force: true });
cy.wait(1000);
cy.getActiveModal(".nc-modal-view-delete")
.find(".ant-btn-dangerous")
.should("exist")
.click();
cy.toastWait("View deleted successfully");
// confirm if the number of veiw entries is reduced by 1
cy.get(".nc-view-item").its("length").should("eq", 1);
// // clean up newly added rows into Country table operations
// // this auto verifies successfull addition of rows to table as well
// mainPage.getPagination(25).click();
//
// cy.get(".nc-grid-row").should("have.length", 1);
// cy.get(".ant-checkbox").should('exist').eq(1).click({ force: true });
// mainPage.getCell("Country", 1).rightclick({ force: true });
// cy.getActiveMenu()
// .contains("Delete Selected Rows")
// .click({ force: true });
});
};
// below scenario's will be invoked twice, once for rest & then for graphql
viewTest("form");
});
};

477
scripts/cypress/integration/common/4f_grid_view_share.js

@ -1,477 +0,0 @@
import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
import { mainPage } from "../../support/page_objects/mainPage";
import { loginPage } from "../../support/page_objects/navigation";
let storedURL = "";
// 0: all enabled
// 1: field hide
// 2: field sort
// 3: field filter
// 4: default (address table): for view operation validation
// 5: default (country table): for update row/column validation
let viewURL = {};
export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return;
const generateViewLink = (viewName) => {
mainPage.shareView().click();
cy.wait(1000);
// wait, as URL initially will be /undefined
cy.getActiveModal(".nc-modal-share-view")
.find(".share-link-box")
.contains("/nc/view/", { timeout: 10000 })
.should("exist");
// copy link text, visit URL
cy.getActiveModal(".nc-modal-share-view")
.find(".share-link-box")
.contains("/nc/view/", { timeout: 10000 })
.then(($obj) => {
// cy.get("body").type("{esc}");
cy.closeActiveModal(".nc-modal-share-view");
// viewURL.push($obj.text())
viewURL[viewName] = $obj.text().trim();
});
cy.getActiveModal(".nc-modal-share-view").should("not.be.visible");
};
let clear;
describe(`${apiType.toUpperCase()} api - GRID view (Share)`, () => {
// Run once before test- create project (rest/graphql)
//
before(() => {
cy.restoreLocalStorage();
cy.openTableTab("Address", 25);
clear = Cypress.LocalStorage.clear;
Cypress.LocalStorage.clear = () => {};
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
after(() => {
// close table
// mainPage.deleteCreatedViews()
cy.restoreLocalStorage();
cy.closeTableTab("Address");
cy.saveLocalStorage();
Cypress.LocalStorage.clear = clear;
});
// Common routine to create/edit/delete GRID & GALLERY view
// Input: viewType - 'grid'/'gallery'
//
const viewTest = (viewType) => {
it(`Create ${viewType.toUpperCase()} view`, () => {
// create a normal public view
cy.get(`.nc-create-${viewType}-view`).click();
cy.getActiveModal(".nc-modal-view-create")
.find("button:contains(Submit)")
.click();
cy.toastWait("View created successfully");
// store base URL- to re-visit and delete form view later
cy.url().then((url) => {
storedURL = url;
});
});
it(`Share ${viewType.toUpperCase()} hide, sort, filter & verify`, () => {
cy.intercept("/api/v1/db/meta/audits/comments/**").as(
"waitForPageLoad"
);
cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Grid-1")
.click();
mainPage.hideField("Address2");
mainPage.sortField("District", "Z → A");
mainPage.filterField("Address", "is like", "Ab");
generateViewLink("combined");
cy.log(viewURL["combined"]);
cy.wait(["@waitForPageLoad"]);
// kludge: additional wait to ensure page load is completed
cy.wait(2000);
});
it(`Share GRID view : ensure we have only one link even if shared multiple times`, () => {
// generate view link multiple times
generateViewLink("combined");
generateViewLink("combined");
// verify if only one link exists in table
mainPage.shareViewList().click();
cy.get('th:contains("View Link")').should("exist");
cy.get('th:contains("View Link")')
.parent()
.parent()
.next()
.find("tr")
.its("length")
.should("eq", 1)
.then(() => {
cy.get("button.ant-modal-close:visible").click();
});
cy.signOut();
});
it(`Share ${viewType.toUpperCase()} view : Visit URL, Verify title`, () => {
// visit public view
cy.visit(viewURL["combined"], {
baseUrl: null,
});
cy.wait(5000);
// wait for page rendering to complete
cy.get(".nc-grid-row").should("have.length", 18);
// verify title
cy.get(".nc-shared-view-title").contains("Grid-1").should("exist");
});
it(`Share ${viewType.toUpperCase()} view : verify fields hidden/open`, () => {
// verify column headers
cy.get('[data-title="Address"]').should("exist");
cy.get('[data-title="Address2"]').should("not.exist");
cy.get('[data-title="District"]').should("exist");
});
it(`Share ${viewType.toUpperCase()} view : verify fields sort/ filter`, () => {
// country column content verification before sort
mainPage
.getCell("District", 1)
.contains("West Bengali")
.should("exist");
mainPage.getCell("District", 2).contains("Tutuila").should("exist");
mainPage.getCell("District", 3).contains("Tamil Nadu").should("exist");
});
it(`Share ${viewType.toUpperCase()} view : verify download CSV`, () => {
mainPage.hideField("LastUpdate");
const verifyCsv = (retrievedRecords) => {
// expected output, statically configured
let storedRecords = [
`Address,District,PostalCode,Phone,Location,Customer List,Staff List,City,Staff List`,
`1013 Tabuk Boulevard,West Bengali,96203,158399646978,[object Object],2,,Kanchrapara,`,
`1892 Nabereznyje Telny Lane,Tutuila,28396,478229987054,[object Object],2,,Tafuna,`,
`1993 Tabuk Lane,Tamil Nadu,64221,648482415405,[object Object],2,,Tambaram,`,
`1661 Abha Drive,Tamil Nadu,14400,270456873752,[object Object],1,,Pudukkottai,`,
];
for (let i = 0; i < storedRecords.length; i++) {
let strCol = storedRecords[i].split(",");
let retCol = retrievedRecords[i].split(",");
for (let j = 0; j < 4; j++) {
expect(strCol[j]).to.be.equal(retCol[j]);
}
}
};
// download & verify
mainPage.downloadAndVerifyCsvFromSharedView(
`Address_exported_1.csv`,
verifyCsv
);
mainPage.unhideField("LastUpdate");
});
it(`Share ${viewType.toUpperCase()} view : Disable sort`, () => {
// remove sort and validate
mainPage.clearSort();
mainPage
.getCell("District", 1)
.contains("West Bengali")
.should("exist");
});
it(`Share ${viewType.toUpperCase()} view : Enable sort`, () => {
// Sort menu operations (Country Column, Z → A)
mainPage.sortField("District", "Z → A");
mainPage
.getCell("District", 1)
.contains("West Bengali")
.should("exist");
});
it(`Share ${viewType.toUpperCase()} view : Create Filter`, () => {
// add filter & validate
mainPage.filterField("District", "is like", "Tamil");
// wait for page rendering to complete
cy.get(".nc-grid-row").should("have.length", 2);
mainPage.getCell("District", 1).contains("Tamil").should("exist");
});
it(`Share ${viewType.toUpperCase()} view : verify download CSV after local filter`, () => {
mainPage.hideField("LastUpdate");
const verifyCsv = (retrievedRecords) => {
// expected output, statically configured
let storedRecords = [
`Address,District,PostalCode,Phone,Location,Customer List,Staff List,City,Staff List`,
`1993 Tabuk Lane,Tamil Nadu,64221,648482415405,[object Object],2,,Tambaram,`,
`1661 Abha Drive,Tamil Nadu,14400,270456873752,[object Object],1,,Pudukkottai,`,
];
// for (let i = 0; i < storedRecords.length; i++) {
// expect(retrievedRecords[i]).to.be.equal(storedRecords[i])
// }
// ncv2@fixme
// for (let i = 0; i < storedRecords.length; i++) {
// let strCol = storedRecords[i].split(",");
// let retCol = retrievedRecords[i].split(",");
// for (let j = 0; j < 4; j++) {
// expect(strCol[j]).to.be.equal(retCol[j]);
// }
// }
};
mainPage.downloadAndVerifyCsvFromSharedView(
`Address_exported_1.csv`,
verifyCsv
);
mainPage.unhideField("LastUpdate");
});
it(`Share ${viewType.toUpperCase()} view : Delete Filter`, () => {
// Remove sort and Validate
mainPage.filterReset();
mainPage
.getCell("District", 1)
.contains("West Bengali")
.should("exist");
});
it(`Share GRID view : Virtual column validation > has many`, () => {
// verify column headers
cy.get('[data-title="Customer List"]').should("exist");
cy.get('[data-title="Staff List"]').should("exist");
cy.get('[data-title="City"]').should("exist");
cy.get('[data-title="Staff List"]').should("exist");
// has many field validation
mainPage
.getCell("Customer List", 3)
.click()
.find(".nc-icon.nc-unlink-icon")
.should("not.exist");
mainPage
.getCell("Customer List", 3)
.click()
.find(".nc-icon.nc-action-icon.nc-plus")
.should("not.exist");
// mainPage
// .getCell("Customer List", 3)
// .click()
// .find(".nc-icon.nc-action-icon.nc-arrow-expand")
// .click({ force: true });
//
// // reload button
// cy.getActiveModal().find(".nc-icon").should("exist");
// cy.getActiveModal()
// .find("button")
// .contains("Link to")
// .should("not.exist");
// cy.getActiveModal()
// .find(".ant-card")
// .contains("2")
// .should("exist");
// cy.getActiveModal()
// .find(".ant-card")
// .find("button")
// .should("not.exist");
// cy.get('button.ant-modal-close').click();
});
it(`Share GRID view : Virtual column validation > belongs to`, () => {
// belongs to field validation
mainPage
.getCell("City", 1)
.click()
.find(".nc-icon.nc-unlink-icon")
.should("not.exist");
mainPage
.getCell("City", 1)
.click()
.find(".nc-icon.nc-action-icon.nc-arrow-expand")
.should("not.exist");
mainPage
.getCell("City", 1)
.find(".chips")
.contains("Kanchrapara")
.should("exist");
});
it(`Share GRID view : Virtual column validation > many to many`, () => {
// many-to-many field validation
mainPage
.getCell("Staff List", 1)
.click()
.find(".nc-icon.nc-unlink-icon")
.should("not.exist");
mainPage
.getCell("Staff List", 1)
.click()
.find(".nc-icon.nc-action-icon.nc-plus")
.should("not.exist");
mainPage
.getCell("Staff List", 1)
.click()
.find(".nc-icon.nc-action-icon.nc-arrow-expand")
.click({ force: true });
// // reload button
// Fix me : ncv2@fixme
// cy.getActiveModal().find(".nc-icon").should("exist");
// cy.getActiveModal()
// .find("button")
// .contains("Link to")
// .should("not.exist");
// cy.get('button.ant-modal-close:visible').last().click();
});
it(`Delete ${viewType.toUpperCase()} view`, () => {
// go back to base page
loginPage.loginAndOpenProject(apiType, dbType);
cy.openTableTab("Address", 25);
// number of view entries should be 2 before we delete
cy.get(".nc-view-item").its("length").should("eq", 2);
cy.get(".nc-view-delete-icon").eq(0).click({ force: true });
cy.getActiveModal(".nc-modal-view-delete")
.find(".ant-btn-dangerous")
.click();
cy.toastWait("View deleted successfully");
// confirm if the number of veiw entries is reduced by 1
cy.get(".nc-view-item").its("length").should("eq", 1);
});
};
// below scenario's will be invoked twice, once for rest & then for graphql
viewTest("grid");
});
describe(`${apiType.toUpperCase()} api - Grid view/ row-column update verification`, () => {
before(() => {
cy.restoreLocalStorage();
// Address table has belongs to, has many & many-to-many
cy.openTableTab("Country", 25);
// store base URL- to re-visit and delete form view later
cy.url().then((url) => {
storedURL = url;
generateViewLink("rowColUpdate");
});
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
after(() => {
cy.restoreLocalStorage();
cy.closeTableTab("Country");
cy.saveLocalStorage();
});
it(`Generate default Shared GRID view URL`, () => {
// add row
mainPage.addNewRowExpand("Country");
cy.get(".nc-expand-col-Country")
.find(".nc-cell > input")
.should("exist")
.first()
.clear({ force: true })
.type("a");
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find("button")
.contains("Save row")
.should("exist")
.click();
cy.toastWait("updated successfully");
cy.getActiveDrawer(".nc-drawer-expanded-form")
.find("button")
.contains("Cancel")
.should("exist")
.click();
// add column
mainPage.addColumn("dummy", "Country");
cy.signOut();
// visit public view
cy.log(viewURL["rowColUpdate"]);
cy.visit(viewURL["rowColUpdate"], {
baseUrl: null,
});
cy.wait(5000);
// wait for public view page to load!
// wait for page rendering to complete
cy.get(".nc-grid-row").should("have.length", 25);
});
it(`Share GRID view : new row visible`, () => {
// verify row
// cy.get(`.v-pagination > li:contains('5') button`).click();
cy.get(
`.nc-pagination > .ant-pagination-item.ant-pagination-item-5`
).click();
// wait for page rendering to complete
cy.get(".nc-grid-row").should("have.length", 10);
mainPage.getCell("Country", 10).contains("a").should("exist");
});
it(`Share GRID view : new column visible`, () => {
// verify column headers
cy.get('[data-title="dummy"]').should("exist");
});
it(`Clean up`, () => {
loginPage.loginAndOpenProject(apiType, dbType);
cy.openTableTab("Country", 25);
// delete row
mainPage.getPagination(5).click();
// kludge: flicker on load
cy.wait(3000);
// wait for page rendering to complete
cy.get(".nc-grid-row").should("have.length", 10);
mainPage.getCell("Country", 10).rightclick();
cy.getActiveMenu(".nc-dropdown-grid-context-menu")
.find('.ant-dropdown-menu-item:contains("Delete Row")')
.first()
.click();
// delete column
mainPage.deleteColumn("dummy");
mainPage.deleteCreatedViews();
});
});
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save