mirror of https://github.com/nocodb/nocodb
Daniel Spaude
2 years ago
41 changed files with 1133 additions and 64 deletions
@ -0,0 +1,120 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import type { UITypes } from 'nocodb-sdk' |
||||||
|
import { AllowedColumnTypesForQrAndBarcodes } from 'nocodb-sdk' |
||||||
|
import type { SelectProps } from 'ant-design-vue' |
||||||
|
import { onMounted, useVModel, watch } from '#imports' |
||||||
|
|
||||||
|
const props = defineProps<{ |
||||||
|
modelValue: any |
||||||
|
}>() |
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']) |
||||||
|
|
||||||
|
const meta = inject(MetaInj, ref()) |
||||||
|
|
||||||
|
const activeView = inject(ActiveViewInj, ref()) |
||||||
|
|
||||||
|
const reloadDataHook = inject(ReloadViewDataHookInj)! |
||||||
|
|
||||||
|
const { fields, metaColumnById } = useViewColumns(activeView, meta, () => reloadDataHook.trigger()) |
||||||
|
|
||||||
|
const vModel = useVModel(props, 'modelValue', emit) |
||||||
|
|
||||||
|
const { setAdditionalValidations, validateInfos, column } = useColumnCreateStoreOrThrow() |
||||||
|
|
||||||
|
const columnsAllowedAsBarcodeValue = computed<SelectProps['options']>(() => { |
||||||
|
return fields.value |
||||||
|
?.filter( |
||||||
|
(el) => |
||||||
|
el.fk_column_id && AllowedColumnTypesForQrAndBarcodes.includes(metaColumnById.value[el.fk_column_id].uidt as UITypes), |
||||||
|
) |
||||||
|
.map((field) => { |
||||||
|
return { |
||||||
|
value: field.fk_column_id, |
||||||
|
label: field.title, |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
const supportedBarcodeFormats = [ |
||||||
|
{ value: 'CODE128', label: 'CODE128' }, |
||||||
|
{ value: 'upc', label: 'UPC' }, |
||||||
|
{ value: 'EAN13', label: 'EAN-13' }, |
||||||
|
{ value: 'EAN8', label: 'EAN-8' }, |
||||||
|
{ value: 'EAN5', label: 'EAN-5' }, |
||||||
|
{ value: 'EAN2', label: 'EAN-2' }, |
||||||
|
{ value: 'CODE39', label: 'CODE39' }, |
||||||
|
{ value: 'ITF14', label: 'ITF-14' }, |
||||||
|
{ value: 'MSI', label: 'MSI' }, |
||||||
|
{ value: 'PHARMACODE', label: 'pharmacode' }, |
||||||
|
{ value: 'CODABAR', label: 'codabar' }, |
||||||
|
] |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
// set default value |
||||||
|
vModel.value.meta = { |
||||||
|
barcodeFormat: supportedBarcodeFormats[0].value, |
||||||
|
...vModel.value.meta, |
||||||
|
} |
||||||
|
vModel.value.fk_barcode_value_column_id = |
||||||
|
(column?.value?.colOptions as Record<string, any>)?.fk_barcode_value_column_id || columnsAllowedAsBarcodeValue.value?.[0] |
||||||
|
}) |
||||||
|
|
||||||
|
watch(columnsAllowedAsBarcodeValue, (newColumnsAllowedAsBarcodeValue) => { |
||||||
|
if (vModel.value.fk_barcode_value_column_id == null) { |
||||||
|
vModel.value.fk_barcode_value_column_id = newColumnsAllowedAsBarcodeValue?.[0]?.value |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
setAdditionalValidations({ |
||||||
|
fk_barcode_value_column_id: [{ required: true, message: 'Required' }], |
||||||
|
barcode_format: [{ required: true, message: 'Required' }], |
||||||
|
}) |
||||||
|
|
||||||
|
const showBarcodeValueColumnInfoIcon = computed(() => !columnsAllowedAsBarcodeValue.value?.length) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-row> |
||||||
|
<a-col :span="24"> |
||||||
|
<a-form-item |
||||||
|
class="flex pb-2 nc-barcode-value-column-select flex-row" |
||||||
|
:label="$t('labels.barcodeValueColumn')" |
||||||
|
v-bind="validateInfos.fk_barcode_value_column_id" |
||||||
|
> |
||||||
|
<div class="flex w-1/2 flex-row items-center"> |
||||||
|
<a-select |
||||||
|
v-model:value="vModel.fk_barcode_value_column_id" |
||||||
|
:options="columnsAllowedAsBarcodeValue" |
||||||
|
placeholder="Select a column for the Barcode value" |
||||||
|
not-found-content="No valid Column Type can be found." |
||||||
|
@click.stop |
||||||
|
/> |
||||||
|
<div v-if="showBarcodeValueColumnInfoIcon" class="pl-2"> |
||||||
|
<a-tooltip placement="bottom"> |
||||||
|
<template #title> |
||||||
|
<span> |
||||||
|
The valid Column Types for a Barcode Column are: Number, Single Line Text, Long Text, Phone Number, URL, Email, |
||||||
|
Decimal. Please create one first. |
||||||
|
</span> |
||||||
|
</template> |
||||||
|
<mdi-information class="cursor-pointer" /> |
||||||
|
</a-tooltip> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</a-form-item> |
||||||
|
<a-form-item |
||||||
|
class="flex w-1/2 pb-2 nc-barcode-format-select" |
||||||
|
:label="$t('labels.barcodeFormat')" |
||||||
|
v-bind="validateInfos.barcode_format" |
||||||
|
> |
||||||
|
<a-select |
||||||
|
v-model:value="vModel.meta.barcodeFormat" |
||||||
|
:options="supportedBarcodeFormats" |
||||||
|
placeholder="Select a Barcode format" |
||||||
|
@click.stop |
||||||
|
/> |
||||||
|
</a-form-item> |
||||||
|
</a-col> |
||||||
|
</a-row> |
||||||
|
</template> |
@ -0,0 +1,68 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import JsBarcodeWrapper from './JsBarcodeWrapper.vue' |
||||||
|
|
||||||
|
const maxNumberOfAllowedCharsForBarcodeValue = 100 |
||||||
|
|
||||||
|
const cellValue = inject(CellValueInj) |
||||||
|
|
||||||
|
const column = inject(ColumnInj) |
||||||
|
|
||||||
|
const barcodeValue: ComputedRef<string> = computed(() => String(cellValue?.value || '')) |
||||||
|
|
||||||
|
const tooManyCharsForBarcode = computed(() => barcodeValue.value.length > maxNumberOfAllowedCharsForBarcodeValue) |
||||||
|
|
||||||
|
const modalVisible = ref(false) |
||||||
|
|
||||||
|
const showBarcodeModal = () => { |
||||||
|
modalVisible.value = true |
||||||
|
} |
||||||
|
|
||||||
|
const barcodeMeta = $computed(() => { |
||||||
|
return { |
||||||
|
barcodeFormat: 'CODE128', |
||||||
|
...(column?.value?.meta || {}), |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const handleModalOkClick = () => (modalVisible.value = false) |
||||||
|
|
||||||
|
const showBarcode = computed(() => barcodeValue?.value.length > 0 && !tooManyCharsForBarcode.value) |
||||||
|
|
||||||
|
const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning } = useShowNotEditableWarning() |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-modal |
||||||
|
v-model:visible="modalVisible" |
||||||
|
:class="{ active: modalVisible }" |
||||||
|
wrap-class-name="nc-barcode-large" |
||||||
|
:body-style="{ padding: '0px' }" |
||||||
|
:footer="null" |
||||||
|
@ok="handleModalOkClick" |
||||||
|
> |
||||||
|
<JsBarcodeWrapper v-if="showBarcode" :barcode-value="barcodeValue" :barcode-format="barcodeMeta.barcodeFormat" /> |
||||||
|
</a-modal> |
||||||
|
<JsBarcodeWrapper |
||||||
|
v-if="showBarcode" |
||||||
|
:barcode-value="barcodeValue" |
||||||
|
:barcode-format="barcodeMeta.barcodeFormat" |
||||||
|
class="nc-barcode-svg" |
||||||
|
@on-click-barcode="showBarcodeModal" |
||||||
|
> |
||||||
|
<template #barcodeRenderError> |
||||||
|
<div class="text-left text-wrap mt-2 text-[#e65100] text-xs" data-testid="barcode-invalid-input-message"> |
||||||
|
{{ $t('msg.warning.barcode.renderError') }} |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</JsBarcodeWrapper> |
||||||
|
|
||||||
|
<div v-if="tooManyCharsForBarcode" class="text-left text-wrap mt-2 text-[#e65100] text-xs"> |
||||||
|
{{ $t('labels.barcodeValueTooLong') }} |
||||||
|
</div> |
||||||
|
<div v-if="showEditNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs"> |
||||||
|
{{ $t('msg.warning.nonEditableFields.computedFieldUnableToClear') }} |
||||||
|
</div> |
||||||
|
<div v-if="showClearNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs"> |
||||||
|
{{ $t('msg.warning.nonEditableFields.barcodeFieldsCannotBeDirectlyChanged') }} |
||||||
|
</div> |
||||||
|
</template> |
@ -0,0 +1,39 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import JsBarcode from 'jsbarcode' |
||||||
|
import { onMounted } from '#imports' |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
barcodeValue: { type: String, required: true }, |
||||||
|
barcodeFormat: { type: String, required: true }, |
||||||
|
}) |
||||||
|
|
||||||
|
const emit = defineEmits(['onClickBarcode']) |
||||||
|
|
||||||
|
const barcodeSvgRef = ref(null) |
||||||
|
const errorForCurrentInput = ref(false) |
||||||
|
|
||||||
|
const generate = () => { |
||||||
|
try { |
||||||
|
JsBarcode(barcodeSvgRef.value, String(props.barcodeValue), { |
||||||
|
format: props.barcodeFormat, |
||||||
|
}) |
||||||
|
errorForCurrentInput.value = false |
||||||
|
} catch (e) { |
||||||
|
console.log('e', e) |
||||||
|
errorForCurrentInput.value = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const onBarcodeClick = (ev: MouseEvent) => { |
||||||
|
ev.stopPropagation() |
||||||
|
emit('onClickBarcode') |
||||||
|
} |
||||||
|
|
||||||
|
watch([() => props.barcodeValue, () => props.barcodeFormat], generate) |
||||||
|
onMounted(generate) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<svg v-show="!errorForCurrentInput" ref="barcodeSvgRef" class="w-full" data-testid="barcode" @click="onBarcodeClick"></svg> |
||||||
|
<slot v-if="errorForCurrentInput" name="barcodeRenderError" /> |
||||||
|
</template> |
@ -1,6 +1,6 @@ |
|||||||
import UITypes from '../UITypes'; |
import UITypes from '../UITypes'; |
||||||
|
|
||||||
export const AllowedColumnTypesForQrCode = [ |
export const AllowedColumnTypesForQrAndBarcodes = [ |
||||||
UITypes.Formula, |
UITypes.Formula, |
||||||
UITypes.SingleLineText, |
UITypes.SingleLineText, |
||||||
UITypes.LongText, |
UITypes.LongText, |
@ -1 +1 @@ |
|||||||
export * from './QrCodeRules'; |
export * from './QrAndBarcodeRules'; |
||||||
|
@ -0,0 +1,26 @@ |
|||||||
|
import { MetaTable } from '../../utils/globals'; |
||||||
|
import { Knex } from 'knex'; |
||||||
|
|
||||||
|
const up = async (knex: Knex) => { |
||||||
|
await knex.schema.createTable(MetaTable.COL_BARCODE, (table) => { |
||||||
|
table.string('id', 20).primary().notNullable(); |
||||||
|
|
||||||
|
table.string('fk_column_id', 20); |
||||||
|
table.foreign('fk_column_id').references(`${MetaTable.COLUMNS}.id`); |
||||||
|
|
||||||
|
table.string('fk_barcode_value_column_id', 20); |
||||||
|
table |
||||||
|
.foreign('fk_barcode_value_column_id') |
||||||
|
.references(`${MetaTable.COLUMNS}.id`); |
||||||
|
|
||||||
|
table.string('barcode_format', 15); |
||||||
|
table.boolean('deleted'); |
||||||
|
table.timestamps(true, true); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const down = async (knex: Knex) => { |
||||||
|
await knex.schema.dropTable(MetaTable.COL_BARCODE); |
||||||
|
}; |
||||||
|
|
||||||
|
export { up, down }; |
@ -0,0 +1,69 @@ |
|||||||
|
import Noco from '../Noco'; |
||||||
|
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; |
||||||
|
import NocoCache from '../cache/NocoCache'; |
||||||
|
import { extractProps } from '../meta/helpers/extractProps'; |
||||||
|
|
||||||
|
export default class BarcodeColumn { |
||||||
|
id: string; |
||||||
|
fk_column_id: string; |
||||||
|
fk_barcode_value_column_id: string; |
||||||
|
barcode_format: string; |
||||||
|
|
||||||
|
constructor(data: Partial<BarcodeColumn>) { |
||||||
|
Object.assign(this, data); |
||||||
|
} |
||||||
|
|
||||||
|
public static async insert( |
||||||
|
data: Partial<BarcodeColumn>, |
||||||
|
ncMeta = Noco.ncMeta |
||||||
|
) { |
||||||
|
await ncMeta.metaInsert2(null, null, MetaTable.COL_BARCODE, { |
||||||
|
fk_column_id: data.fk_column_id, |
||||||
|
fk_barcode_value_column_id: data.fk_barcode_value_column_id, |
||||||
|
barcode_format: data.barcode_format, |
||||||
|
}); |
||||||
|
|
||||||
|
return this.read(data.fk_column_id, ncMeta); |
||||||
|
} |
||||||
|
public static async read(columnId: string, ncMeta = Noco.ncMeta) { |
||||||
|
let column = |
||||||
|
columnId && |
||||||
|
(await NocoCache.get( |
||||||
|
`${CacheScope.COL_BARCODE}:${columnId}`, |
||||||
|
CacheGetType.TYPE_OBJECT |
||||||
|
)); |
||||||
|
if (!column) { |
||||||
|
column = await ncMeta.metaGet2( |
||||||
|
null, //,
|
||||||
|
null, //model.db_alias,
|
||||||
|
MetaTable.COL_BARCODE, |
||||||
|
{ fk_column_id: columnId } |
||||||
|
); |
||||||
|
await NocoCache.set(`${CacheScope.COL_BARCODE}:${columnId}`, column); |
||||||
|
} |
||||||
|
|
||||||
|
return column ? new BarcodeColumn(column) : null; |
||||||
|
} |
||||||
|
|
||||||
|
static async update( |
||||||
|
id: string, |
||||||
|
barcode: Partial<BarcodeColumn>, |
||||||
|
ncMeta = Noco.ncMeta |
||||||
|
) { |
||||||
|
const updateObj = extractProps(barcode, [ |
||||||
|
'fk_column_id', |
||||||
|
'fk_barcode_value_column_id', |
||||||
|
'barcode_format', |
||||||
|
]); |
||||||
|
// get existing cache
|
||||||
|
const key = `${CacheScope.COL_BARCODE}:${id}`; |
||||||
|
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT); |
||||||
|
if (o) { |
||||||
|
o = { ...o, ...updateObj }; |
||||||
|
// set cache
|
||||||
|
await NocoCache.set(key, o); |
||||||
|
} |
||||||
|
// set meta
|
||||||
|
await ncMeta.metaUpdate(null, null, MetaTable.COL_BARCODE, updateObj, id); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,176 @@ |
|||||||
|
import { expect } from 'chai' |
||||||
|
import fs from 'fs' |
||||||
|
import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk' |
||||||
|
import path from 'path' |
||||||
|
import 'mocha' |
||||||
|
import request from 'supertest' |
||||||
|
import { createProject } from '../../factory/project' |
||||||
|
import init from '../../init' |
||||||
|
|
||||||
|
const FILE_PATH = path.join(__dirname, 'test.txt') |
||||||
|
|
||||||
|
function attachmentTests() { |
||||||
|
let context |
||||||
|
|
||||||
|
|
||||||
|
beforeEach(async function() { |
||||||
|
context = await init() |
||||||
|
fs.writeFileSync(FILE_PATH, 'test', `utf-8`) |
||||||
|
context = await init() |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
afterEach(function() { |
||||||
|
fs.unlinkSync(FILE_PATH) |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
it('Upload file - Super admin', async () => { |
||||||
|
const response = await request(context.app) |
||||||
|
.post('/api/v1/db/storage/upload') |
||||||
|
.attach('files', FILE_PATH) |
||||||
|
.set('xc-auth', context.token) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
|
||||||
|
const attachments = response.body |
||||||
|
expect(attachments).to.be.an('array') |
||||||
|
expect(attachments[0].title).to.be.eq(path.basename(FILE_PATH)) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Upload file - Without token', async () => { |
||||||
|
const response = await request(context.app) |
||||||
|
.post('/api/v1/db/storage/upload') |
||||||
|
.attach('files', FILE_PATH) |
||||||
|
.expect(401) |
||||||
|
|
||||||
|
const msg = response.body.msg |
||||||
|
expect(msg).to.be.eq('Unauthorized') |
||||||
|
}) |
||||||
|
|
||||||
|
it('Upload file - Org level viewer', async () => { |
||||||
|
|
||||||
|
// signup a user
|
||||||
|
const args = { |
||||||
|
email: 'dummyuser@example.com', |
||||||
|
password: 'A1234abh2@dsad', |
||||||
|
} |
||||||
|
|
||||||
|
const signupResponse = await request(context.app) |
||||||
|
.post('/api/v1/auth/user/signup') |
||||||
|
.send(args) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
const response = await request(context.app) |
||||||
|
.post('/api/v1/db/storage/upload') |
||||||
|
.attach('files', FILE_PATH) |
||||||
|
.set('xc-auth', signupResponse.body.token) |
||||||
|
.expect(400) |
||||||
|
|
||||||
|
const msg = response.body.msg |
||||||
|
expect(msg).to.be.eq('Upload not allowed') |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
it('Upload file - Org level creator', async () => { |
||||||
|
|
||||||
|
// signup a user
|
||||||
|
const args = { |
||||||
|
email: 'dummyuser@example.com', |
||||||
|
password: 'A1234abh2@dsad', |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
await request(context.app) |
||||||
|
.post('/api/v1/auth/user/signup') |
||||||
|
.send(args) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
// update user role to creator
|
||||||
|
const usersListResponse = await request(context.app) |
||||||
|
.get('/api/v1/users') |
||||||
|
.set('xc-auth', context.token) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
const user = usersListResponse.body.list.find(u => u.email === args.email) |
||||||
|
|
||||||
|
expect(user).to.have.property('roles').to.be.equal(OrgUserRoles.VIEWER) |
||||||
|
|
||||||
|
|
||||||
|
await request(context.app) |
||||||
|
.patch('/api/v1/users/' + user.id) |
||||||
|
.set('xc-auth', context.token) |
||||||
|
.send({ roles: OrgUserRoles.CREATOR }) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
|
||||||
|
const signinResponse = await request(context.app) |
||||||
|
.post('/api/v1/auth/user/signin') |
||||||
|
// pass empty data in await request
|
||||||
|
.send(args) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
const response = await request(context.app) |
||||||
|
.post('/api/v1/db/storage/upload') |
||||||
|
.attach('files', FILE_PATH) |
||||||
|
.set('xc-auth', signinResponse.body.token) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
const attachments = response.body |
||||||
|
expect(attachments).to.be.an('array') |
||||||
|
expect(attachments[0].title).to.be.eq(path.basename(FILE_PATH)) |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
it('Upload file - Org level viewer with editor role in a project', async () => { |
||||||
|
|
||||||
|
// signup a new user
|
||||||
|
const args = { |
||||||
|
email: 'dummyuser@example.com', |
||||||
|
password: 'A1234abh2@dsad', |
||||||
|
} |
||||||
|
|
||||||
|
await request(context.app) |
||||||
|
.post('/api/v1/auth/user/signup') |
||||||
|
.send(args) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
const newProject = await createProject(context, { |
||||||
|
title: 'NewTitle1', |
||||||
|
}) |
||||||
|
|
||||||
|
// invite user to project with editor role
|
||||||
|
await request(context.app) |
||||||
|
.post(`/api/v1/db/meta/projects/${newProject.id}/users`) |
||||||
|
.set('xc-auth', context.token) |
||||||
|
.send({ |
||||||
|
roles: ProjectRoles.EDITOR, |
||||||
|
email: args.email, |
||||||
|
project_id: newProject.id, |
||||||
|
projectName: newProject.title, |
||||||
|
}) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
// signin to get user token
|
||||||
|
const signinResponse = await request(context.app) |
||||||
|
.post('/api/v1/auth/user/signin') |
||||||
|
// pass empty data in await request
|
||||||
|
.send(args) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
const response = await request(context.app) |
||||||
|
.post('/api/v1/db/storage/upload') |
||||||
|
.attach('files', FILE_PATH) |
||||||
|
.set('xc-auth', signinResponse.body.token) |
||||||
|
.expect(200) |
||||||
|
|
||||||
|
const attachments = response.body |
||||||
|
expect(attachments).to.be.an('array') |
||||||
|
expect(attachments[0].title).to.be.eq(path.basename(FILE_PATH)) |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
export default function() { |
||||||
|
describe('Attachment', attachmentTests) |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
import { expect } from '@playwright/test'; |
||||||
|
import BasePage from '../../Base'; |
||||||
|
import { FormPage } from '../Form'; |
||||||
|
import { GalleryPage } from '../Gallery'; |
||||||
|
import { GridPage } from '../Grid'; |
||||||
|
import { KanbanPage } from '../Kanban'; |
||||||
|
|
||||||
|
export class BarcodeOverlay extends BasePage { |
||||||
|
constructor(parent: GridPage | GalleryPage | KanbanPage | FormPage) { |
||||||
|
super(parent.rootPage); |
||||||
|
} |
||||||
|
|
||||||
|
get() { |
||||||
|
return this.rootPage.locator(`.nc-barcode-large`); |
||||||
|
} |
||||||
|
|
||||||
|
async verifyBarcodeSvgValue(expectedValue: string) { |
||||||
|
const foundBarcodeSvg = await this.get().getByTestId('barcode').innerHTML(); |
||||||
|
await expect(foundBarcodeSvg).toContain(expectedValue); |
||||||
|
} |
||||||
|
|
||||||
|
async clickCloseButton() { |
||||||
|
await this.get().locator('.ant-modal-close-x').click(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,174 @@ |
|||||||
|
import { expect, test } from '@playwright/test'; |
||||||
|
import { DashboardPage } from '../pages/Dashboard'; |
||||||
|
import setup from '../setup'; |
||||||
|
import { GridPage } from '../pages/Dashboard/Grid'; |
||||||
|
|
||||||
|
interface ExpectedBarcodeData { |
||||||
|
referencedValue: string; |
||||||
|
barcodeSvg: string; |
||||||
|
} |
||||||
|
|
||||||
|
test.describe('Virtual Columns', () => { |
||||||
|
let dashboard: DashboardPage; |
||||||
|
let grid: GridPage; |
||||||
|
let context: any; |
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => { |
||||||
|
context = await setup({ page }); |
||||||
|
dashboard = new DashboardPage(page, context.project); |
||||||
|
grid = dashboard.grid; |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('Barcode Column', () => { |
||||||
|
const initiallyExpectedBarcodeCellValues: ExpectedBarcodeData[] = [ |
||||||
|
{ |
||||||
|
referencedValue: 'A Corua (La Corua)', |
||||||
|
barcodeSvg: |
||||||
|
'<rect x="0" y="0" width="486" height="142" style="fill:#ffffff;"></rect><g transform="translate(10, 10)" style="fill:#000000;"><rect x="0" y="0" width="4" height="100"></rect><rect x="6" y="0" width="2" height="100"></rect><rect x="12" y="0" width="2" height="100"></rect><rect x="22" y="0" width="2" height="100"></rect><rect x="26" y="0" width="2" height="100"></rect><rect x="34" y="0" width="4" height="100"></rect><rect x="44" y="0" width="4" height="100"></rect><rect x="50" y="0" width="4" height="100"></rect><rect x="58" y="0" width="4" height="100"></rect><rect x="66" y="0" width="2" height="100"></rect><rect x="74" y="0" width="2" height="100"></rect><rect x="82" y="0" width="4" height="100"></rect><rect x="88" y="0" width="2" height="100"></rect><rect x="96" y="0" width="8" height="100"></rect><rect x="106" y="0" width="2" height="100"></rect><rect x="110" y="0" width="2" height="100"></rect><rect x="116" y="0" width="2" height="100"></rect><rect x="122" y="0" width="8" height="100"></rect><rect x="132" y="0" width="2" height="100"></rect><rect x="138" y="0" width="8" height="100"></rect><rect x="150" y="0" width="2" height="100"></rect><rect x="154" y="0" width="2" height="100"></rect><rect x="160" y="0" width="2" height="100"></rect><rect x="164" y="0" width="4" height="100"></rect><rect x="176" y="0" width="4" height="100"></rect><rect x="182" y="0" width="4" height="100"></rect><rect x="190" y="0" width="4" height="100"></rect><rect x="198" y="0" width="2" height="100"></rect><rect x="206" y="0" width="4" height="100"></rect><rect x="214" y="0" width="2" height="100"></rect><rect x="220" y="0" width="2" height="100"></rect><rect x="228" y="0" width="4" height="100"></rect><rect x="234" y="0" width="6" height="100"></rect><rect x="242" y="0" width="2" height="100"></rect><rect x="248" y="0" width="2" height="100"></rect><rect x="252" y="0" width="4" height="100"></rect><rect x="264" y="0" width="4" height="100"></rect><rect x="270" y="0" width="4" height="100"></rect><rect x="278" y="0" width="4" height="100"></rect><rect x="286" y="0" width="2" height="100"></rect><rect x="294" y="0" width="2" height="100"></rect><rect x="302" y="0" width="4" height="100"></rect><rect x="308" y="0" width="2" height="100"></rect><rect x="316" y="0" width="8" height="100"></rect><rect x="326" y="0" width="2" height="100"></rect><rect x="330" y="0" width="2" height="100"></rect><rect x="336" y="0" width="2" height="100"></rect><rect x="342" y="0" width="8" height="100"></rect><rect x="352" y="0" width="2" height="100"></rect><rect x="358" y="0" width="8" height="100"></rect><rect x="370" y="0" width="2" height="100"></rect><rect x="374" y="0" width="2" height="100"></rect><rect x="380" y="0" width="2" height="100"></rect><rect x="384" y="0" width="4" height="100"></rect><rect x="396" y="0" width="4" height="100"></rect><rect x="404" y="0" width="2" height="100"></rect><rect x="410" y="0" width="2" height="100"></rect><rect x="418" y="0" width="6" height="100"></rect><rect x="428" y="0" width="4" height="100"></rect><rect x="436" y="0" width="2" height="100"></rect><rect x="440" y="0" width="4" height="100"></rect><rect x="450" y="0" width="6" height="100"></rect><rect x="458" y="0" width="2" height="100"></rect><rect x="462" y="0" width="4" height="100"></rect><text style="font: 20px monospace" text-anchor="middle" x="233" y="122">A Corua (La Corua)</text></g>', |
||||||
|
}, |
||||||
|
{ |
||||||
|
referencedValue: 'Abha', |
||||||
|
barcodeSvg: |
||||||
|
'<rect x="0" y="0" width="178" height="142" style="fill:#ffffff;"></rect><g transform="translate(10, 10)" style="fill:#000000;"><rect x="0" y="0" width="4" height="100"></rect><rect x="6" y="0" width="2" height="100"></rect><rect x="12" y="0" width="2" height="100"></rect><rect x="22" y="0" width="2" height="100"></rect><rect x="26" y="0" width="2" height="100"></rect><rect x="34" y="0" width="4" height="100"></rect><rect x="44" y="0" width="2" height="100"></rect><rect x="50" y="0" width="2" height="100"></rect><rect x="60" y="0" width="4" height="100"></rect><rect x="66" y="0" width="2" height="100"></rect><rect x="72" y="0" width="4" height="100"></rect><rect x="84" y="0" width="2" height="100"></rect><rect x="88" y="0" width="2" height="100"></rect><rect x="94" y="0" width="2" height="100"></rect><rect x="98" y="0" width="4" height="100"></rect><rect x="110" y="0" width="6" height="100"></rect><rect x="118" y="0" width="2" height="100"></rect><rect x="124" y="0" width="4" height="100"></rect><rect x="132" y="0" width="4" height="100"></rect><rect x="142" y="0" width="6" height="100"></rect><rect x="150" y="0" width="2" height="100"></rect><rect x="154" y="0" width="4" height="100"></rect><text style="font: 20px monospace" text-anchor="middle" x="79" y="122">Abha</text></g>', |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
const barcodeCellValuesForBerlin = { |
||||||
|
referencedValue: 'Berlin', |
||||||
|
barcodeSvg: |
||||||
|
'<rect x="0" y="0" width="222" height="142" style="fill:#ffffff;"></rect><g transform="translate(10, 10)" style="fill:#000000;"><rect x="0" y="0" width="4" height="100"></rect><rect x="6" y="0" width="2" height="100"></rect><rect x="12" y="0" width="2" height="100"></rect><rect x="22" y="0" width="2" height="100"></rect><rect x="30" y="0" width="2" height="100"></rect><rect x="34" y="0" width="4" height="100"></rect><rect x="44" y="0" width="2" height="100"></rect><rect x="48" y="0" width="4" height="100"></rect><rect x="56" y="0" width="2" height="100"></rect><rect x="66" y="0" width="2" height="100"></rect><rect x="72" y="0" width="2" height="100"></rect><rect x="78" y="0" width="8" height="100"></rect><rect x="88" y="0" width="4" height="100"></rect><rect x="96" y="0" width="2" height="100"></rect><rect x="100" y="0" width="2" height="100"></rect><rect x="110" y="0" width="2" height="100"></rect><rect x="120" y="0" width="4" height="100"></rect><rect x="126" y="0" width="2" height="100"></rect><rect x="132" y="0" width="4" height="100"></rect><rect x="144" y="0" width="2" height="100"></rect><rect x="148" y="0" width="2" height="100"></rect><rect x="154" y="0" width="4" height="100"></rect><rect x="164" y="0" width="2" height="100"></rect><rect x="170" y="0" width="2" height="100"></rect><rect x="176" y="0" width="4" height="100"></rect><rect x="186" y="0" width="6" height="100"></rect><rect x="194" y="0" width="2" height="100"></rect><rect x="198" y="0" width="4" height="100"></rect><text style="font: 20px monospace" text-anchor="middle" x="101" y="122">Berlin</text></g>', |
||||||
|
}; |
||||||
|
|
||||||
|
const barcodeCellValuesForIstanbul = { |
||||||
|
referencedValue: 'Istanbul', |
||||||
|
barcodeSvg: |
||||||
|
'<rect x="0" y="0" width="266" height="142" style="fill:#ffffff;"></rect><g transform="translate(10, 10)" style="fill:#000000;"><rect x="0" y="0" width="4" height="100"></rect><rect x="6" y="0" width="2" height="100"></rect><rect x="12" y="0" width="2" height="100"></rect><rect x="22" y="0" width="4" height="100"></rect><rect x="32" y="0" width="2" height="100"></rect><rect x="40" y="0" width="2" height="100"></rect><rect x="44" y="0" width="2" height="100"></rect><rect x="48" y="0" width="8" height="100"></rect><rect x="60" y="0" width="2" height="100"></rect><rect x="66" y="0" width="2" height="100"></rect><rect x="72" y="0" width="8" height="100"></rect><rect x="82" y="0" width="2" height="100"></rect><rect x="88" y="0" width="2" height="100"></rect><rect x="94" y="0" width="2" height="100"></rect><rect x="98" y="0" width="4" height="100"></rect><rect x="110" y="0" width="4" height="100"></rect><rect x="122" y="0" width="2" height="100"></rect><rect x="126" y="0" width="2" height="100"></rect><rect x="132" y="0" width="2" height="100"></rect><rect x="138" y="0" width="2" height="100"></rect><rect x="148" y="0" width="4" height="100"></rect><rect x="154" y="0" width="2" height="100"></rect><rect x="160" y="0" width="8" height="100"></rect><rect x="172" y="0" width="2" height="100"></rect><rect x="176" y="0" width="4" height="100"></rect><rect x="184" y="0" width="2" height="100"></rect><rect x="188" y="0" width="2" height="100"></rect><rect x="198" y="0" width="4" height="100"></rect><rect x="204" y="0" width="4" height="100"></rect><rect x="214" y="0" width="4" height="100"></rect><rect x="220" y="0" width="4" height="100"></rect><rect x="230" y="0" width="6" height="100"></rect><rect x="238" y="0" width="2" height="100"></rect><rect x="242" y="0" width="4" height="100"></rect><text style="font: 20px monospace" text-anchor="middle" x="123" y="122">Istanbul</text></g>', |
||||||
|
}; |
||||||
|
|
||||||
|
const barcodeCode39SvgForBerlin = |
||||||
|
'<rect x="0" y="0" width="276" height="142" style="fill:#ffffff;"></rect><g transform="translate(10, 10)" style="fill:#000000;"><rect x="0" y="0" width="2" height="100"></rect><rect x="8" y="0" width="2" height="100"></rect><rect x="12" y="0" width="6" height="100"></rect><rect x="20" y="0" width="6" height="100"></rect><rect x="28" y="0" width="2" height="100"></rect><rect x="32" y="0" width="2" height="100"></rect><rect x="36" y="0" width="6" height="100"></rect><rect x="44" y="0" width="2" height="100"></rect><rect x="52" y="0" width="2" height="100"></rect><rect x="56" y="0" width="6" height="100"></rect><rect x="64" y="0" width="6" height="100"></rect><rect x="72" y="0" width="2" height="100"></rect><rect x="76" y="0" width="6" height="100"></rect><rect x="88" y="0" width="2" height="100"></rect><rect x="92" y="0" width="2" height="100"></rect><rect x="96" y="0" width="6" height="100"></rect><rect x="104" y="0" width="2" height="100"></rect><rect x="108" y="0" width="2" height="100"></rect><rect x="112" y="0" width="6" height="100"></rect><rect x="124" y="0" width="2" height="100"></rect><rect x="128" y="0" width="2" height="100"></rect><rect x="132" y="0" width="6" height="100"></rect><rect x="140" y="0" width="2" height="100"></rect><rect x="144" y="0" width="2" height="100"></rect><rect x="152" y="0" width="6" height="100"></rect><rect x="160" y="0" width="2" height="100"></rect><rect x="164" y="0" width="6" height="100"></rect><rect x="172" y="0" width="2" height="100"></rect><rect x="180" y="0" width="6" height="100"></rect><rect x="188" y="0" width="2" height="100"></rect><rect x="192" y="0" width="2" height="100"></rect><rect x="196" y="0" width="2" height="100"></rect><rect x="200" y="0" width="6" height="100"></rect><rect x="208" y="0" width="2" height="100"></rect><rect x="216" y="0" width="6" height="100"></rect><rect x="224" y="0" width="2" height="100"></rect><rect x="232" y="0" width="2" height="100"></rect><rect x="236" y="0" width="6" height="100"></rect><rect x="244" y="0" width="6" height="100"></rect><rect x="252" y="0" width="2" height="100"></rect><text style="font: 20px monospace" text-anchor="middle" x="128" y="122">BERLIN</text></g>'; |
||||||
|
|
||||||
|
const expectedBarcodeCellValuesAfterCityNameChange = [ |
||||||
|
barcodeCellValuesForBerlin, |
||||||
|
...initiallyExpectedBarcodeCellValues.slice(1), |
||||||
|
]; |
||||||
|
|
||||||
|
async function barcodeColumnVerify(barcodeColumnTitle: string, expectedBarcodeCodeData: ExpectedBarcodeData[]) { |
||||||
|
for (let i = 0; i < expectedBarcodeCodeData.length; i++) { |
||||||
|
await grid.cell.verifyBarcodeCell({ |
||||||
|
index: i, |
||||||
|
columnHeader: barcodeColumnTitle, |
||||||
|
expectedSvgValue: expectedBarcodeCodeData[i].barcodeSvg, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
test('creation, showing, updating value and change barcode column title and reference column', async () => { |
||||||
|
// Add barcode code column referencing the City column
|
||||||
|
// and compare the base64 encoded codes/src attributes for the first 3 rows.
|
||||||
|
// Column data from City table (Sakila DB)
|
||||||
|
/** |
||||||
|
* City LastUpdate Address List Country |
||||||
|
* A Corua (La Corua) 2006-02-15 04:45:25 939 Probolinggo Loop Spain |
||||||
|
* Abha 2006-02-15 04:45:25 733 Mandaluyong Place Saudi Arabia |
||||||
|
* Abu Dhabi 2006-02-15 04:45:25 535 Ahmadnagar Manor United Arab Emirates |
||||||
|
*/ |
||||||
|
// close 'Team & Auth' tab
|
||||||
|
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||||
|
|
||||||
|
await dashboard.treeView.openTable({ title: 'City' }); |
||||||
|
|
||||||
|
await grid.column.create({ |
||||||
|
title: 'Barcode1', |
||||||
|
type: 'Barcode', |
||||||
|
barcodeValueColumnTitle: 'City', |
||||||
|
}); |
||||||
|
|
||||||
|
await barcodeColumnVerify('Barcode1', initiallyExpectedBarcodeCellValues); |
||||||
|
|
||||||
|
await grid.cell.fillText({ columnHeader: 'City', index: 0, text: 'Berlin' }); |
||||||
|
|
||||||
|
await barcodeColumnVerify('Barcode1', expectedBarcodeCellValuesAfterCityNameChange); |
||||||
|
|
||||||
|
await grid.cell.get({ columnHeader: 'Barcode1', index: 0 }).click(); |
||||||
|
const barcodeGridOverlay = grid.barcodeOverlay; |
||||||
|
await barcodeGridOverlay.verifyBarcodeSvgValue(barcodeCellValuesForBerlin.barcodeSvg); |
||||||
|
await barcodeGridOverlay.clickCloseButton(); |
||||||
|
|
||||||
|
// Change the barcode column title
|
||||||
|
await grid.column.openEdit({ title: 'Barcode1' }); |
||||||
|
await grid.column.fillTitle({ title: 'Barcode1 Renamed' }); |
||||||
|
await grid.column.save({ isUpdated: true }); |
||||||
|
await barcodeColumnVerify('Barcode1 Renamed', expectedBarcodeCellValuesAfterCityNameChange); |
||||||
|
|
||||||
|
// Change the referenced column title
|
||||||
|
await grid.column.openEdit({ title: 'City' }); |
||||||
|
await grid.column.fillTitle({ title: 'City Renamed' }); |
||||||
|
await grid.column.save({ isUpdated: true }); |
||||||
|
await barcodeColumnVerify('Barcode1 Renamed', expectedBarcodeCellValuesAfterCityNameChange); |
||||||
|
|
||||||
|
// Change the referenced column
|
||||||
|
await grid.column.create({ title: 'New City Column' }); |
||||||
|
await grid.cell.fillText({ columnHeader: 'New City Column', index: 0, text: 'Istanbul' }); |
||||||
|
await grid.column.openEdit({ title: 'Barcode1 Renamed' }); |
||||||
|
await grid.column.changeReferencedColumnForBarcode({ titleOfReferencedColumn: 'New City Column' }); |
||||||
|
|
||||||
|
await barcodeColumnVerify('Barcode1 Renamed', [barcodeCellValuesForIstanbul]); |
||||||
|
|
||||||
|
await dashboard.closeTab({ title: 'City' }); |
||||||
|
}); |
||||||
|
|
||||||
|
test('deletion of the barcode column: a) directly and b) indirectly when the reference value column is deleted', async () => { |
||||||
|
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||||
|
|
||||||
|
await dashboard.treeView.openTable({ title: 'City' }); |
||||||
|
|
||||||
|
await grid.column.create({ title: 'column_name_a' }); |
||||||
|
await grid.column.verify({ title: 'column_name_a' }); |
||||||
|
await grid.column.create({ |
||||||
|
title: 'Barcode2', |
||||||
|
type: 'Barcode', |
||||||
|
barcodeValueColumnTitle: 'column_name_a', |
||||||
|
}); |
||||||
|
await grid.column.verify({ title: 'Barcode2', isVisible: true }); |
||||||
|
await grid.column.delete({ title: 'Barcode2' }); |
||||||
|
await grid.column.verify({ title: 'Barcode2', isVisible: false }); |
||||||
|
|
||||||
|
await grid.column.create({ |
||||||
|
title: 'Barcode2', |
||||||
|
type: 'Barcode', |
||||||
|
barcodeValueColumnTitle: 'column_name_a', |
||||||
|
}); |
||||||
|
await grid.column.verify({ title: 'Barcode2', isVisible: true }); |
||||||
|
await grid.column.delete({ title: 'column_name_a' }); |
||||||
|
await grid.column.verify({ title: 'Barcode2', isVisible: false }); |
||||||
|
|
||||||
|
await dashboard.closeTab({ title: 'City' }); |
||||||
|
}); |
||||||
|
|
||||||
|
test('a) showing an error message for non-compatible barcode input and b) changing the format of the Barcode is reflected in the change of the actual rendered barcode', async () => { |
||||||
|
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||||
|
|
||||||
|
await dashboard.treeView.openTable({ title: 'City' }); |
||||||
|
|
||||||
|
await grid.column.create({ |
||||||
|
title: 'Barcode1', |
||||||
|
type: 'Barcode', |
||||||
|
barcodeValueColumnTitle: 'City', |
||||||
|
}); |
||||||
|
|
||||||
|
await grid.column.openEdit({ |
||||||
|
title: 'Barcode1', |
||||||
|
}); |
||||||
|
await grid.column.changeBarcodeFormat({ barcodeFormatName: 'CODE39' }); |
||||||
|
|
||||||
|
await grid.cell.verifyBarcodeCellShowsInvalidInputMessage({ |
||||||
|
index: 0, |
||||||
|
columnHeader: 'Barcode1', |
||||||
|
}); |
||||||
|
|
||||||
|
await grid.cell.fillText({ columnHeader: 'City', index: 0, text: 'Berlin' }); |
||||||
|
|
||||||
|
await barcodeColumnVerify('Barcode1', [{ referencedValue: 'Berlin', barcodeSvg: barcodeCode39SvgForBerlin }]); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue