UBERF-8993: Part2 (#7532)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-12-24 17:39:51 +07:00 committed by GitHub
parent 0db7adccd6
commit 35981be705
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 178 additions and 114 deletions

1
.gitignore vendored
View File

@ -106,3 +106,4 @@ tests/profiles
dump
**/logs/**
dev/tool/history.json
.aider*

View File

@ -28,22 +28,23 @@ function $push (document: Doc, keyval: Record<string, PropertyType>): void {
if (doc[key] === undefined) {
doc[key] = []
}
const val = keyval[key]
if (typeof val === 'object') {
const kvk = keyval[key]
if (typeof kvk === 'object' && kvk != null) {
const arr = doc[key] as Array<any>
const desc = val as Position<PropertyType>
const desc = kvk as Position<PropertyType>
if ('$each' in desc) {
if (arr != null) {
if (arr != null && Array.isArray(arr)) {
arr.splice(desc.$position ?? 0, 0, ...desc.$each)
}
} else {
arr.push(val)
arr.push(kvk)
}
} else {
if (doc[key] == null) {
doc[key] = []
if (doc[key] === null || doc[key] === undefined) {
doc[key] = [kvk]
} else {
doc[key].push(kvk)
}
doc[key].push(val)
}
}
}
@ -55,15 +56,16 @@ function $pull (document: Doc, keyval: Record<string, PropertyType>): void {
doc[key] = []
}
const arr = doc[key] as Array<any>
if (typeof keyval[key] === 'object' && keyval[key] !== null) {
const { $in } = keyval[key] as PullArray<PropertyType>
const kvk = keyval[key]
if (typeof kvk === 'object' && kvk !== null) {
const { $in } = kvk as PullArray<PropertyType>
doc[key] = (arr ?? []).filter((val) => {
if ($in !== undefined) {
return !$in.includes(val)
} else {
// We need to match all fields
for (const [kk, kv] of Object.entries(keyval[key])) {
for (const [kk, kv] of Object.entries(kvk)) {
if (val[kk] !== kv) {
return true
}
@ -72,7 +74,7 @@ function $pull (document: Doc, keyval: Record<string, PropertyType>): void {
}
})
} else {
doc[key] = (arr ?? []).filter((val) => val !== keyval[key])
doc[key] = (arr ?? []).filter((val) => val !== kvk)
}
}
}

View File

@ -93,13 +93,14 @@
dispatch('update', selectedObjects)
}
const client = getClient()
let selection = 0
let list: ListView
async function handleSelection (evt: Event | undefined, objects: Doc[], selection: number): Promise<void> {
const item = objects[selection]
if (item === undefined) {
return
}
if (!multiSelect) {
if (allowDeselect) {
@ -140,7 +141,7 @@
showPopup(c.component, c.props ?? {}, 'top', async (res) => {
if (res != null) {
// We expect reference to new object.
const newPerson = await client.findOne(_class, { _id: res })
const newPerson = await getClient().findOne(_class, { _id: res })
if (newPerson !== undefined) {
search = c.update?.(newPerson) ?? ''
dispatch('created', newPerson)
@ -163,7 +164,7 @@
}
function findObjectPresenter (_class: Ref<Class<Doc>>): void {
const presenterMixin = client.getHierarchy().classHierarchyMixin(_class, view.mixin.ObjectPresenter)
const presenterMixin = getClient().getHierarchy().classHierarchyMixin(_class, view.mixin.ObjectPresenter)
if (presenterMixin?.presenter !== undefined) {
getResource(presenterMixin.presenter)
.then((result) => {

View File

@ -46,7 +46,6 @@
let categories: ObjectSearchCategory[] = []
let categoryStatus: Record<Ref<ObjectSearchCategory>, number> = {}
const client = getClient()
let category: ObjectSearchCategory | undefined
const categoryQuery: DocumentQuery<ObjectSearchCategory> = {
@ -56,10 +55,12 @@
categoryQuery._id = { $in: allowCategory }
}
client.findAll(presentation.class.ObjectSearchCategory, categoryQuery).then((r) => {
categories = r.filter((it) => hasResource(it.query))
category = categories[0]
})
void getClient()
.findAll(presentation.class.ObjectSearchCategory, categoryQuery)
.then((r) => {
categories = r.filter((it) => hasResource(it.query))
category = categories[0]
})
const dispatch = createEventDispatcher()
@ -96,7 +97,7 @@
key.preventDefault()
key.stopPropagation()
const item = items[selection]
if (item) {
if (item != null) {
dispatchItem(item)
return true
} else {
@ -106,7 +107,7 @@
return false
}
export function done () {}
export function done (): void {}
const updateItems = reduceCalls(async function updateItems (
cat: ObjectSearchCategory | undefined,
@ -120,6 +121,7 @@
const newCategoryStatus: Record<Ref<ObjectSearchCategory>, number> = {}
const f = await getResource(cat.query)
const client = getClient()
const result = await f(client, query, { in: relatedDocuments, nin: ignore })
// We need to sure, results we return is for proper category.
if (cat._id === category?._id) {

View File

@ -53,7 +53,7 @@ import { getRawCurrentLocation, workspaceId, type AnyComponent, type AnySvelteCo
import view, { type AttributeCategory, type AttributeEditor } from '@hcengineering/view'
import { deepEqual } from 'fast-equals'
import { onDestroy } from 'svelte'
import { get, writable, type Writable } from 'svelte/store'
import { get, writable } from 'svelte/store'
import { type KeyedAttribute } from '..'
import { OptimizeQueryMiddleware, PresentationPipelineImpl, type PresentationPipeline } from './pipeline'
@ -63,7 +63,7 @@ export { reduceCalls } from '@hcengineering/core'
let liveQuery: LQ
let rawLiveQuery: LQ
let client: TxOperations & Client & OptimisticTxes
let client: TxOperations & Client
let pipeline: PresentationPipeline
const txListeners: Array<(...tx: Tx[]) => void> = []
@ -89,13 +89,11 @@ export function removeTxListener (l: (tx: Tx) => void): void {
}
}
export interface OptimisticTxes {
pendingCreatedDocs: Writable<Record<Ref<Doc>, boolean>>
}
export const uiContext = new MeasureMetricsContext('client-ui', {})
class UIClient extends TxOperations implements Client, OptimisticTxes {
export const pendingCreatedDocs = writable<Record<Ref<Doc>, boolean>>({})
class UIClient extends TxOperations implements Client {
hook = getMetadata(plugin.metadata.ClientHook)
constructor (
client: Client,
@ -105,14 +103,9 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
}
protected pendingTxes = new Set<Ref<Tx>>()
protected _pendingCreatedDocs = writable<Record<Ref<Doc>, boolean>>({})
get pendingCreatedDocs (): typeof this._pendingCreatedDocs {
return this._pendingCreatedDocs
}
async doNotify (...tx: Tx[]): Promise<void> {
const pending = get(this._pendingCreatedDocs)
const pending = get(pendingCreatedDocs)
let pendingUpdated = false
tx.forEach((t) => {
if (this.pendingTxes.has(t._id)) {
@ -129,7 +122,7 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
}
})
if (pendingUpdated) {
this._pendingCreatedDocs.set(pending)
pendingCreatedDocs.set(pending)
}
// We still want to notify about all transactions because there might be queries created after
@ -214,9 +207,9 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
}
if (innerTx._class === core.class.TxCreateDoc) {
const pending = get(this._pendingCreatedDocs)
const pending = get(pendingCreatedDocs)
pending[innerTx.objectId] = true
this._pendingCreatedDocs.set(pending)
pendingCreatedDocs.set(pending)
}
this.pendingTxes.add(tx._id)
@ -231,11 +224,33 @@ class UIClient extends TxOperations implements Client, OptimisticTxes {
}
}
const hierarchyProxy = new Proxy(
{},
{
get (target, p, receiver) {
const h = client.getHierarchy()
return Reflect.get(h, p)
}
}
) as TxOperations & Client
// We need a proxy to handle all the calls to the proper client.
const clientProxy = new Proxy(
{},
{
get (target, p, receiver) {
if (p === 'getHierarchy') {
return () => hierarchyProxy
}
return Reflect.get(client, p)
}
}
) as TxOperations & Client
/**
* @public
*/
export function getClient (): TxOperations & Client & OptimisticTxes {
return client
export function getClient (): TxOperations & Client {
return clientProxy
}
let txQueue: Tx[] = []
@ -252,6 +267,7 @@ export function addRefreshListener (r: RefreshListener): void {
* @public
*/
export async function setClient (_client: Client): Promise<void> {
pendingCreatedDocs.set({})
if (liveQuery !== undefined) {
await liveQuery.close()
}
@ -700,7 +716,7 @@ export function isSpace (space: Doc): space is Space {
}
export function isSpaceClass (_class: Ref<Class<Doc>>): boolean {
return getClient().getHierarchy().isDerived(_class, core.class.Space)
return client.getHierarchy().isDerived(_class, core.class.Space)
}
export function setPresentationCookie (token: string, workspaceId: string): void {

View File

@ -59,6 +59,9 @@
}
function onKeydown (e: KeyboardEvent): void {
if (e.key === undefined) {
return
}
const key = e.key.toLowerCase()
const target = e.target as HTMLInputElement
if (key !== 'backspace' && key !== 'delete') return

View File

@ -63,7 +63,7 @@ class FocusManagerImpl implements FocusManager {
return
}
this.current = this.elements.findIndex((it) => it.id === idx) ?? 0
this.elements[Math.abs(this.current) % this.elements.length].focus()
this.elements[Math.abs(this.current) % this.elements.length]?.focus()
}
setFocusPos (order: number): void {
@ -73,7 +73,7 @@ class FocusManagerImpl implements FocusManager {
const idx = this.elements.findIndex((it) => it.order === order)
if (idx !== undefined) {
this.current = idx
this.elements[Math.abs(this.current) % this.elements.length].focus()
this.elements[Math.abs(this.current) % this.elements.length]?.focus()
}
}

View File

@ -149,16 +149,17 @@ export function closePopup (category?: string): void {
} else {
for (let i = popups.length - 1; i >= 0; i--) {
if (popups[i].type !== 'popup') continue
if ((popups[i] as CompAndProps).options.fixed !== true) {
const isClosing = (popups[i] as CompAndProps).closing ?? false
const popi = popups[i] as CompAndProps
if (popi.options.fixed !== true) {
const isClosing = popi.closing ?? false
if (popups[i].type === 'popup') {
;(popups[i] as CompAndProps).closing = true
popi.closing = true
}
if (!isClosing) {
// To prevent possible recursion, we need to check if we call some code from popup close, to do close.
;(popups[i] as CompAndProps).onClose?.(undefined)
popi.onClose?.(undefined)
}
;(popups[i] as CompAndProps).closing = false
popi.closing = false
popups.splice(i, 1)
break
}

View File

@ -214,8 +214,12 @@ export function replaceURLs (text: string): string {
* @returns {string} string with parsed URL
*/
export function parseURL (text: string): string {
const matches = autolinker.parse(text, { urls: true })
return matches.length > 0 ? matches[0].getAnchorHref() : ''
try {
const matches = autolinker.parse(text ?? '', { urls: true })
return matches.length > 0 ? matches[0].getAnchorHref() : ''
} catch (err: any) {
return ''
}
}
/**

View File

@ -16,7 +16,7 @@
import contact, { Person, PersonAccount } from '@hcengineering/contact'
import { personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
import { Class, Doc, getCurrentAccount, Markup, Ref, Space, WithLookup } from '@hcengineering/core'
import { getClient, MessageViewer } from '@hcengineering/presentation'
import { getClient, MessageViewer, pendingCreatedDocs } from '@hcengineering/presentation'
import { AttachmentDocList, AttachmentImageSize } from '@hcengineering/attachment-resources'
import { getDocLinkTitle } from '@hcengineering/view-resources'
import { Action, Button, IconEdit, ShowMore } from '@hcengineering/ui'
@ -58,7 +58,6 @@
export let onReply: ((message: ActivityMessage) => void) | undefined = undefined
const client = getClient()
const { pendingCreatedDocs } = client
const hierarchy = client.getHierarchy()
const STALE_TIMEOUT_MS = 5000
const currentAccount = getCurrentAccount()

View File

@ -82,9 +82,11 @@
included = []
}
$: employees = Array.from(
(value ?? []).map((it) => $personAccountByIdStore.get(it as Ref<PersonAccount>)?.person)
).filter((it) => it !== undefined) as Ref<Employee>[]
$: employees = Array.isArray(value)
? (Array.from((value ?? []).map((it) => $personAccountByIdStore.get(it as Ref<PersonAccount>)?.person)).filter(
(it) => it !== undefined
) as Ref<Employee>[])
: []
$: docQuery =
excluded.length === 0 && included.length === 0

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Contact, Employee, Person } from '@hcengineering/contact'
import contact, { Person } from '@hcengineering/contact'
import type { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
import type { IntlString } from '@hcengineering/platform'
import { ObjectCreate, getClient } from '@hcengineering/presentation'
@ -43,8 +43,8 @@
export let sort: ((a: Person, b: Person) => number) | undefined = undefined
function filter (items: Ref<Person>[]): Ref<Person>[] {
return items.filter((it, idx, arr) => arr.indexOf(it) === idx)
function filter (items: Ref<Person>[] | undefined): Ref<Person>[] {
return (items ?? []).filter((it, idx, arr) => arr.indexOf(it) === idx)
}
let persons: Person[] = filter(items)
@ -55,11 +55,10 @@
.filter((p) => p !== undefined) as Person[]
const dispatch = createEventDispatcher()
const client = getClient()
async function addPerson (evt: Event): Promise<void> {
const accounts = new Set(
client
getClient()
.getModel()
.findAllSync(contact.class.PersonAccount, {})
.map((p) => p.person)
@ -72,7 +71,7 @@
allowDeselect: false,
selectedUsers: filter(items),
filter: (it: Doc) => {
const h = client.getHierarchy()
const h = getClient().getHierarchy()
if (h.hasMixin(it, contact.mixin.Employee)) {
const isActive = h.as(it, contact.mixin.Employee).active
const isSelected = items.some((selectedItem) => selectedItem === it._id)

View File

@ -46,7 +46,7 @@
if (space !== value.space) {
const children = await findChildren(value)
for (const child of children) {
await client.updateDoc(document.class.Document, value.space, child, {
await ops.updateDoc(document.class.Document, value.space, child, {
space
})
}

View File

@ -1058,7 +1058,11 @@ export async function restorePassword (token: string, password: string): Promise
}
async function handleStatusError (message: string, err: Status): Promise<void> {
if (err.code === platform.status.InvalidPassword || err.code === platform.status.AccountNotFound) {
if (
err.code === platform.status.InvalidPassword ||
err.code === platform.status.AccountNotFound ||
err.code === platform.status.InvalidOtp
) {
// No need to send to analytics
return
}

View File

@ -340,7 +340,7 @@
console.log(`Error exiting fullscreen mode: ${err.message} (${err.name})`)
$isFullScreen = false
})
} else if (!document.fullscreenElement && needFullScreen) {
} else if (!document.fullscreenElement && needFullScreen && roomEl != null) {
roomEl
.requestFullscreen()
.then(() => {
@ -355,7 +355,7 @@
function onFullScreen (): void {
const needFullScreen = !$isFullScreen
if (!document.fullscreenElement && needFullScreen) {
if (!document.fullscreenElement && needFullScreen && roomEl != null) {
roomEl
.requestFullscreen()
.then(() => {

View File

@ -15,7 +15,15 @@
-->
<script lang="ts">
import { AttachmentStyleBoxCollabEditor } from '@hcengineering/attachment-resources'
import core, { ClassifierKind, type CollaborativeDoc, Data, Doc, Mixin, Ref } from '@hcengineering/core'
import core, {
ClassifierKind,
type CollaborativeDoc,
Data,
Doc,
type MarkupBlobRef,
Mixin,
Ref
} from '@hcengineering/core'
import notification from '@hcengineering/notification'
import { Panel } from '@hcengineering/panel'
import { getResource } from '@hcengineering/platform'
@ -37,7 +45,7 @@
let object: Required<Vacancy>
let rawName: string = ''
let rawDesc: string = ''
let rawFullDesc: CollaborativeDoc
let rawFullDesc: MarkupBlobRef | null = null
let lastId: Ref<Vacancy> | undefined = undefined
let showAllMixins = false

View File

@ -35,10 +35,8 @@
export let groupByKey: string
export let config: (string | BuildModelKey)[]
const client = getClient()
const hierarchy = client.getHierarchy()
const assigneeAttribute = hierarchy.getAttribute(recruit.class.Applicant, 'assignee')
const isTitleHidden = client.getHierarchy().getAttribute(recruit.mixin.Candidate, 'title').hidden
const assigneeAttribute = getClient().getHierarchy().getAttribute(recruit.class.Applicant, 'assignee')
const isTitleHidden = getClient().getHierarchy().getAttribute(recruit.mixin.Candidate, 'title').hidden
$: channels = (object.$lookup?.attachedTo as WithLookup<Candidate>)?.$lookup?.channels
@ -70,7 +68,7 @@
<Avatar person={object.$lookup?.attachedTo} size={'medium'} name={object.$lookup?.attachedTo?.name} />
<div class="flex-grow flex-col min-w-0 ml-2">
<div class="fs-title over-underline lines-limit-2">
{object.$lookup?.attachedTo ? getName(client.getHierarchy(), object.$lookup.attachedTo) : ''}
{object.$lookup?.attachedTo ? getName(getClient().getHierarchy(), object.$lookup.attachedTo) : ''}
</div>
{#if !isTitleHidden && enabledConfig(config, 'title')}
<div class="text-sm lines-limit-2">{object.$lookup?.attachedTo?.title ?? ''}</div>
@ -107,7 +105,7 @@
shrink={1}
value={object.status}
onChange={(status) => {
client.update(object, { status })
getClient().update(object, { status })
}}
/>
{/if}
@ -120,7 +118,7 @@
shouldRender={object.dueDate !== null && object.dueDate !== undefined}
shouldIgnoreOverdue={isDone}
onChange={async (e) => {
await client.update(object, { dueDate: e })
await getClient().update(object, { dueDate: e })
}}
/>
{/if}

View File

@ -122,7 +122,7 @@
}
const handleSelect = async (event: CustomEvent): Promise<void> => {
selected = event.detail as AnyAttribute
if (selected != null) {
if (selected?._id != null) {
const exist = (await client.findOne(selected.attributeOf, { [selected.name]: { $exists: true } })) !== undefined
$settingsStore = { id: selected._id, component: EditAttribute, props: { attribute: selected, exist, disabled } }
}

View File

@ -55,7 +55,9 @@
specials = newSpecials
}
$: updateSpecials(model, space)
$: if (model != null) {
void updateSpecials(model, space)
}
$: visible =
(!deselect && currentSpace !== undefined && currentSpecial !== undefined && space._id === currentSpace) ||
forciblyСollapsed
@ -64,7 +66,7 @@
{#if specials}
<TreeNode
_id={space?._id}
icon={space?.icon === view.ids.IconWithEmoji ? IconWithEmoji : space?.icon ?? model.icon}
icon={space?.icon === view.ids.IconWithEmoji ? IconWithEmoji : space?.icon ?? model?.icon}
iconProps={space?.icon === view.ids.IconWithEmoji
? { icon: space.color }
: {

View File

@ -108,22 +108,22 @@ export const completionConfig: Partial<CompletionOptions> = {
props: {
...props,
close: () => {
component.destroy()
component?.destroy()
}
}
})
},
onUpdate (props: SuggestionProps) {
component.updateProps(props)
component?.updateProps(props)
},
onKeyDown (props: SuggestionKeyDownProps) {
if (props.event.key === 'Escape') {
props.event.stopPropagation()
}
return component.onKeyDown(props)
return component?.onKeyDown(props)
},
onExit () {
component.destroy()
component?.destroy()
}
}
}
@ -181,22 +181,22 @@ export function inlineCommandsConfig (
props: {
...props,
close: () => {
component.destroy()
component?.destroy()
}
}
})
},
onUpdate (props: SuggestionProps) {
component.updateProps(props)
component?.updateProps(props)
},
onKeyDown (props: SuggestionKeyDownProps) {
if (props.event.key === 'Escape') {
props.event.stopPropagation()
}
return component.onKeyDown(props)
return component?.onKeyDown(props)
},
onExit () {
component.destroy()
component?.destroy()
}
}
}

View File

@ -55,7 +55,9 @@
specials = newSpecials
}
$: updateSpecials(model, space)
$: if (model != null) {
void updateSpecials(model, space)
}
$: visible =
(!deselect && currentSpace !== undefined && currentSpecial !== undefined && space._id === currentSpace) ||
forciblyСollapsed

View File

@ -30,8 +30,6 @@
let resActions = actions
const client = getClient()
let loaded = false
const order: Record<ActionGroup, number> = {
@ -44,7 +42,7 @@
remove: 7
}
void getActions(client, object, baseMenuClass, mode).then((result) => {
void getActions(getClient(), object, baseMenuClass, mode).then((result) => {
const filtered = result.filter((a) => {
if (excludedActions.includes(a._id)) {
return false
@ -63,7 +61,7 @@
inline: a.inline,
group: a.context.group ?? 'other',
action: async (_: any, evt: Event) => {
if (overrides.has(a._id)) {
if (overrides?.has(a._id)) {
overrides.get(a._id)?.(object, evt)
return
}

View File

@ -178,11 +178,11 @@
const keyDown = (event: KeyboardEvent, index: number) => {
if (event.key === 'ArrowDown') {
actionElements[(index + 1) % actionElements.length].focus()
actionElements[(index + 1) % actionElements.length]?.focus()
}
if (event.key === 'ArrowUp') {
actionElements[(actionElements.length + index - 1) % actionElements.length].focus()
actionElements[(actionElements.length + index - 1) % actionElements.length]?.focus()
}
if (event.key === 'ArrowLeft') {

View File

@ -604,6 +604,7 @@ export class DocumentContentPage extends DocumentCommonPage {
await expect(this.editDocumentSpace).not.toBeVisible()
await expect(this.createNewTemplateFromSpace).not.toBeVisible()
}
await this.page.keyboard.press('Escape')
}
async checkSpaceFormIsCreated (spaceName: string): Promise<void> {

View File

@ -31,6 +31,7 @@ import core, {
Data,
generateId,
getWorkspaceId,
isActiveMode,
isArchivingMode,
isMigrationMode,
isWorkspaceCreating,
@ -511,7 +512,7 @@ export async function selectWorkspace (
}
return result
}
if (workspaceInfo.disabled === true && workspaceInfo.mode === 'active') {
if (workspaceInfo.disabled === true && isActiveMode(workspaceInfo.mode)) {
ctx.error('workspace disabled', { workspaceUrl, email })
throw new PlatformError(
new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace: workspaceUrl })
@ -1770,14 +1771,14 @@ export async function getWorkspaceInfo (
const [ws] = await ctx.with('get-workspace', {}, async () =>
(await db.workspace.find(query)).filter(
(it) => it.disabled !== true || account?.admin === true || it.mode !== 'active'
(it) => it.disabled !== true || account?.admin === true || !isActiveMode(it.mode)
)
)
if (ws == null) {
ctx.error('no workspace', { workspace: workspace.name, email })
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}
if (ws.mode !== 'archived' && _updateLastVisit && (isAccount(account) || email === systemAccountEmail)) {
if (!isArchivingMode(ws.mode) && _updateLastVisit && (isAccount(account) || email === systemAccountEmail)) {
void ctx.with('update-last-visit', {}, () => updateLastVisit(db, ws, account as Account))
}

View File

@ -204,12 +204,17 @@ test.describe('Content in the Documents tests', () => {
await expect(documentContentPage.imageInPopup()).toBeVisible()
await documentContentPage.fullscreenButton().click()
await expect(documentContentPage.fullscreenImage()).toBeVisible()
await documentContentPage.page.keyboard.press('Escape')
await expect(documentContentPage.fullscreenImage()).toBeHidden()
await expect(async () => {
await documentContentPage.page.keyboard.press('Escape')
await expect(documentContentPage.fullscreenImage()).toBeHidden()
await expect(documentContentPage.imageInPopup()).toBeHidden()
}).toPass(retryOptions)
})
await test.step('User can open image original in the new tab', async () => {
const pagePromise = context.waitForEvent('page')
const pagePromise = context.waitForEvent('page', {
timeout: 15000
})
await documentContentPage.clickImageOriginalButton()
const newPage = await pagePromise

View File

@ -29,6 +29,7 @@ test.describe('Tracker issue tests', () => {
const newIssue: NewIssue = {
title: `Issue with all parameters and attachments-${generateId()}`,
description: 'Created issue with all parameters and attachments description',
projectName: 'Default',
status: 'In Progress',
priority: 'Urgent',
assignee: 'Appleseed John',
@ -48,7 +49,8 @@ test.describe('Tracker issue tests', () => {
test('Edit an issue', async ({ page }) => {
const newIssue: NewIssue = {
title: `Issue with all parameters and attachments-${generateId()}`,
description: 'Created issue with all parameters and attachments description'
description: 'Created issue with all parameters and attachments description',
projectName: 'Default'
}
const editIssue: Issue = {
status: 'Done',
@ -86,7 +88,8 @@ test.describe('Tracker issue tests', () => {
test.skip('Set parent issue', async ({ page }) => {
const parentIssue: NewIssue = {
title: `PARENT ISSUE-${generateId(2)}`,
description: 'Created issue to be parent issue'
description: 'Created issue to be parent issue',
projectName: 'Default'
}
await issuesPage.clickModelSelectorAll()
@ -96,7 +99,8 @@ test.describe('Tracker issue tests', () => {
const newIssue: NewIssue = {
title: `Set parent issue during creation-${generateId(2)}`,
description: 'Set parent issue during creation',
parentIssue: parentIssue.title
parentIssue: parentIssue.title,
projectName: 'Default'
}
await issuesPage.clickModelSelectorAll()
@ -116,7 +120,8 @@ test.describe('Tracker issue tests', () => {
await test.step('Set parent issue from issues page', async () => {
const newIssue: NewIssue = {
title: `Set parent issue from issues page-${generateId(2)}`,
description: 'Set parent issue from issues page'
description: 'Set parent issue from issues page',
projectName: 'Default'
}
await issuesPage.clickModelSelectorAll()
await issuesPage.createNewIssue(newIssue)
@ -139,7 +144,8 @@ test.describe('Tracker issue tests', () => {
await test.step('Set parent issue from issue details page', async () => {
const newIssue: NewIssue = {
title: `Set parent issue from issue details page-${generateId(2)}`,
description: 'Set parent issue from issue details page'
description: 'Set parent issue from issue details page',
projectName: 'Default'
}
await issuesPage.clickModelSelectorAll()
await issuesPage.createNewIssue(newIssue)
@ -160,7 +166,8 @@ test.describe('Tracker issue tests', () => {
const secondProjectName = 'Second Project'
const moveIssue: NewIssue = {
title: `Issue to another project-${generateId()}`,
description: 'Issue to move to another project'
description: 'Issue to move to another project',
projectName: 'Default'
}
await prepareNewIssueWithOpenStep(page, moveIssue)
await issuesDetailsPage.moreActionOnIssue('Move to project')
@ -181,7 +188,8 @@ test.describe('Tracker issue tests', () => {
const commentText = `Comment should be stored after reload-${generateId()}`
const commentIssue: NewIssue = {
title: `Issue for stored comment-${generateId()}`,
description: 'Issue for comment stored after reload the page'
description: 'Issue for comment stored after reload the page',
projectName: 'Default'
}
await prepareNewIssueWithOpenStep(page, commentIssue)
@ -203,7 +211,8 @@ test.describe('Tracker issue tests', () => {
priority: 'Medium',
estimation: '8',
component: 'Default component',
milestone: 'Edit Milestone'
milestone: 'Edit Milestone',
projectName: 'Default'
}
await issuesPage.clickModelSelectorAll()
await issuesPage.buttonCreateNewIssue().click()
@ -219,8 +228,9 @@ test.describe('Tracker issue tests', () => {
test('Delete an issue', async ({ page }) => {
const deleteIssue: NewIssue = {
title: `Issue-to-delete-${generateId()}`,
description: 'Description Issue for deletion'
title: `Issue-to-delete-${generateId(4)}`,
description: 'Description Issue for deletion',
projectName: 'Default'
}
await prepareNewIssueWithOpenStep(page, deleteIssue)
await issuesPage.navigateToIssues()
@ -238,7 +248,8 @@ test.describe('Tracker issue tests', () => {
const additionalDescription = 'New row for the additional description'
const changedDescriptionIssue: NewIssue = {
title: `Check the changed description activity-${generateId()}`,
description: 'Check the changed description activity description'
description: 'Check the changed description activity description',
projectName: 'Default'
}
await prepareNewIssueWithOpenStep(page, changedDescriptionIssue)
await issuesDetailsPage.waitDetailsOpened(changedDescriptionIssue.title)
@ -251,7 +262,8 @@ test.describe('Tracker issue tests', () => {
test('Add comment with image attachment', async ({ page }) => {
const commentImageIssue: NewIssue = {
title: `Add comment with image attachment-${generateId()}`,
description: 'Add comment with image attachment'
description: 'Add comment with image attachment',
projectName: 'Default'
}
await prepareNewIssueWithOpenStep(page, commentImageIssue)
await issuesDetailsPage.waitDetailsOpened(commentImageIssue.title)
@ -264,7 +276,8 @@ test.describe('Tracker issue tests', () => {
const commentPopup = `Comment for the popup-${generateId()}`
const commentIssue: NewIssue = {
title: `Issue for add comment by popup-${generateId()}`,
description: 'Issue for add comment by popup'
description: 'Issue for add comment by popup',
projectName: 'Default'
}
await prepareNewIssueWithOpenStep(page, commentIssue)
await issuesDetailsPage.waitDetailsOpened(commentIssue.title)

View File

@ -9,7 +9,8 @@ test.describe('Tracker public link issues tests', () => {
test('Public link generate', async ({ browser }) => {
const publicLinkIssue: NewIssue = {
title: `Public link generate issue-${generateId()}`,
description: 'Public link generate issue'
description: 'Public link generate issue',
projectName: 'Default'
}
let link: string
@ -60,7 +61,8 @@ test.describe('Tracker public link issues tests', () => {
test('Public link Revoke', async ({ browser }) => {
const publicLinkIssue: NewIssue = {
title: `Public link Revoke issue-${generateId()}`,
description: 'Public link Revoke issue'
description: 'Public link Revoke issue',
projectName: 'Default'
}
const newContext = await browser.newContext({ storageState: PlatformSetting })