Skip to content

Commit

Permalink
fix: lazy render list and table templates (#585)
Browse files Browse the repository at this point in the history
* fix: lazy render list and table templates

* fix: test improvements

* fix: trying to fix tests

* fix: test fixes

* fix: lint issue

* fix: add types jest node

* fix: remove types of tscondig lib

* fix: remove promise frtom test

* fix: build error global var

* fix: lint var let issue

---------

Co-authored-by: kim.tran <[email protected]>
Co-authored-by: SchettlerKoehler <[email protected]>
Co-authored-by: Kim Tran <[email protected]>
  • Loading branch information
4 people authored Nov 13, 2024
1 parent f02dac2 commit cc0faea
Show file tree
Hide file tree
Showing 18 changed files with 185 additions and 22 deletions.
1 change: 1 addition & 0 deletions libs/angular-accelerator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@onecx/integration-interface": "^5",
"@onecx/angular-integration-interface": "^5",
"@onecx/angular-remote-components": "^5",
"@onecx/angular-testing": "^5",
"chart.js": "^4.4.3",
"d3-scale-chromatic": "^3.1.0",
"rxjs": "~7.8.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@
<ng-template #listItem let-rows pTemplate="listItem">
<div class="p-grid p-nogutter grid grid-nogutter">
<ng-template ngFor let-item [ngForOf]="rows">
@defer (on viewport; on idle){
<ng-container
[ngTemplateOutlet]="_listItem ? _listItem : defaultListItem"
[ngTemplateOutletContext]="{
item:item,
columnTemplates: columnTemplates
}"
></ng-container>
} @placeholder {
<div style="width: 100%; height: 80px"></div>
}
</ng-template>
</div>
</ng-template>
Expand Down Expand Up @@ -87,7 +91,7 @@
</div>
</ng-template>
<ng-template #defaultListItem let-item="item" let-columnTemplates="columnTemplates">
<div class="col-12">
<div class="col-12" *ngIf="columnTemplates">
<div class="data-list-items p-3">
<div class="item-name-row flex flex-row justify-content-between">
<div class="item-name mr-3 text-2xl font-medium align-content-center">
Expand Down Expand Up @@ -190,16 +194,19 @@
[ocxTooltipOnOverflow]="col.columnType === columnType.TRANSLATION_KEY ? (resolveFieldData(item,col.id) | translate) : resolveFieldData(item, col.id)"
tooltipPosition="top"
>
@defer(on viewport;){
<ng-container
[ngTemplateOutlet]="
_listValue ? _listValue: defaultListValue
"
[ngTemplateOutlet]="_listValue ? _listValue: defaultListValue"
[ngTemplateOutletContext]="{
rowObject: item,
column: col,
columnTemplates: columnTemplates
}"
></ng-container>
}"
>
</ng-container>
} @placeholder {
<p-skeleton width="5rem" />
}
</div>
</div>
</div>
Expand All @@ -214,6 +221,7 @@

<ng-template #defaultListValue let-rowObject="rowObject" let-column="column" let-columnTemplates="columnTemplates">
<ng-container
*ngIf="columnTemplates[column.id]"
[ngTemplateOutlet]="columnTemplates[column.id]"
[ngTemplateOutletContext]="{
rowObject: rowObject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ export class DataListGridComponent extends DataSortBase implements OnInit, DoChe
this._columns$.next(value)
const obs = value.map((c) => this.getTemplate(c))
this.columnTemplates$ = combineLatest(obs).pipe(
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]])))
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]]))),
debounceTime(50)
)
}
@Input() name = ''
Expand Down Expand Up @@ -648,8 +649,7 @@ export class DataListGridComponent extends DataSortBase implements OnInit, DoChe
null
)
}
}),
debounceTime(50)
})
)
}
return this.templatesObservables[column.id]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowObject>
<tr>
<tr *ngIf="columnTemplates">
<td *ngIf="selectionChangedObserved">
<p-checkbox
*ngIf="isRowSelectionDisabled(rowObject) && isSelected(rowObject); else defaultCheckbox"
Expand All @@ -254,15 +254,21 @@
<ng-container *ngTemplateOutlet="actionColumn; context: {localRowObject: rowObject}"></ng-container>
</ng-container>
<td *ngFor="let column of columns">
@defer(on viewport){
<ng-container
*ngIf="columnTemplates[column.id]"
[ngTemplateOutlet]="
_cell ? _cell: columnTemplates[column.id]
_cell ? _cell: columnTemplates[column.id]
"
[ngTemplateOutletContext]="{
rowObject: rowObject,
column: column,
}"
></ng-container>
>
</ng-container>
} @placeholder {
<p-skeleton width="3rem" />
}
</td>
<ng-container *ngIf="actionColumnPosition === 'right';">
<ng-container *ngTemplateOutlet="actionColumn; context: {localRowObject: rowObject}"></ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon
const obs = value.map((c) => this.getTemplate(c, TemplateType.CELL))
const filterObs = value.map((c) => this.getTemplate(c, TemplateType.FILTERCELL))
this.columnTemplates$ = combineLatest(obs).pipe(
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]])))
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]]))),
debounceTime(50)
)
this.columnFilterTemplates$ = combineLatest(filterObs).pipe(
map((values) => Object.fromEntries(value.map((c, i) => [c.id, values[i]])))
Expand Down Expand Up @@ -908,8 +909,7 @@ export class DataTableComponent extends DataSortBase implements OnInit, AfterCon
return columnTemplate
}
return this.getColumnTypeTemplate(templates, column.columnType, templateType)
}),
debounceTime(50)
})
)
}
return templatesData.templatesObservables[column.id]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ describe('InteractiveDataViewComponent', () => {
viewItemEvent = undefined
editItemEvent = undefined
deleteItemEvent = undefined

console.log("Global IntersectionObserver", global.IntersectionObserver)
})

it('should create', () => {
Expand Down Expand Up @@ -433,7 +435,6 @@ describe('InteractiveDataViewComponent', () => {
]
const sortButton = await tableHeaders[0].getSortButton()
await sortButton.click()

tableRows = (await dataTable?.getRows()) ?? []
const rows = await parallel(() => tableRows.map((row) => row.getData()))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { of } from 'rxjs'
import { Filter, FilterType } from '../../model/filter.model'
import { FilterViewComponent } from '../filter-view/filter-view.component'
import { FocusTrapModule } from 'primeng/focustrap'
import { TooltipOnOverflowDirective } from '../../directives/tooltipOnOverflow.directive'
import { SkeletonModule } from 'primeng/skeleton'

type InteractiveDataViewInputTypes = Pick<
InteractiveDataViewComponent,
Expand Down Expand Up @@ -85,6 +87,7 @@ const InteractiveDataViewComponentSBConfig: Meta<InteractiveDataViewComponent> =
DataListGridComponent,
DataListGridSortingComponent,
FilterViewComponent,
TooltipOnOverflowDirective
],
imports: [
TableModule,
Expand All @@ -105,6 +108,7 @@ const InteractiveDataViewComponentSBConfig: Meta<InteractiveDataViewComponent> =
OverlayPanelModule,
FocusTrapModule,
ChipModule,
SkeletonModule
],
}),
],
Expand Down Expand Up @@ -192,6 +196,71 @@ export const WithMockData = {
},
}

function generateColumns(count: number) {
const data = []
for (let i = 0; i < count; i++) {
const row = {
id: `${i + 1}`,
columnType: ColumnType.STRING,
nameKey: `Product${i + 1}`,
sortable: false,
filterable: true,
predefinedGroupKeys: ['test'],
};
data.push(row)
}
return data
}

function generateRows(rowCount: number, columnCount: number) {
const data = []
for (let i = 0; i < rowCount; i++) {
const row = {} as any
for(let j = 0; j < columnCount; j++) {
row[j+1] = `Test value for ${j+1}`
}
data.push(row)
}
return data
}

function generateColumnTemplates(columnCount: number) {
let templates = ''
for(let i = 0; i < columnCount; i++) {
templates += `
<ng-template pTemplate="${i+1}IdListValue" let-rowObject="rowObject" let-column="column">
<ng-container>${i+1} {{ rowObject[${i+1}] }} </ng-container>
</ng-template>`
}
return templates
}

const columnCount = 30;
const rowCount = 500

const HugeMockDataTemplate: StoryFn<InteractiveDataViewComponent> = (args) => ({
props: args,
template: `
<ocx-interactive-data-view [emptyResultsMessage]="emptyResultsMessage" [columns]="columns" [data]="data">
${generateColumnTemplates(Math.ceil(columnCount/3))}
</ocx-interactive-data-view>`,
})

export const WithHugeMockData = {
argTypes: {
componentStateChanged: { action: 'componentStateChanged' },
selectionChanged: { action: 'selectionChanged' },
},
render: HugeMockDataTemplate,
args: {
columns: generateColumns(columnCount),
data: generateRows(rowCount, columnCount),
emptyResultsMessage: 'No results',
selectedRows: [],
pageSize: 50,
},
}

export const WithPageSizes = {
argTypes: {
componentStateChanged: { action: 'componentStateChanged' },
Expand Down
7 changes: 6 additions & 1 deletion libs/angular-accelerator/testing/data-list-grid.harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ import { ContentContainerComponentHarness, TestElement, parallel } from '@angula
import { PPaginatorHarness } from '@onecx/angular-testing'
import { DefaultGridItemHarness } from './default-grid-item.harness'
import { DefaultListItemHarness } from './default-list-item.harness'
import { waitForDeferredViewsToBeRendered } from '@onecx/angular-testing'

export class DataListGridHarness extends ContentContainerComponentHarness {
static hostSelector = 'ocx-data-list-grid'

getDefaultGridItems = this.locatorForAll(DefaultGridItemHarness)
getDefaultListItems = this.locatorForAll(DefaultListItemHarness)
getPaginator = this.locatorFor(PPaginatorHarness)
getMenuButton = this.locatorFor(`[name="data-grid-item-menu-button"]`)

async getDefaultListItems() {
await waitForDeferredViewsToBeRendered(this)
return await this.locatorForAll(DefaultListItemHarness)()
}

async getActionButtons(actionButtonType: 'list' | 'grid' | 'grid-hidden') {
if (actionButtonType === 'list') {
return await this.locatorForAll(`[name="data-list-action-button"]`)()
Expand Down
2 changes: 2 additions & 0 deletions libs/angular-accelerator/testing/default-list-item.harness.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ComponentHarness } from '@angular/cdk/testing'
import { ButtonHarness, DivHarness } from '@onecx/angular-testing'
import { waitForDeferredViewsToBeRendered } from '@onecx/angular-testing'

export class DefaultListItemHarness extends ComponentHarness {
static hostSelector = '.data-list-items'
Expand All @@ -12,6 +13,7 @@ export class DefaultListItemHarness extends ComponentHarness {
private getAllDivs = this.locatorForAll(DivHarness)

async getData() {
await waitForDeferredViewsToBeRendered(this)
const isDataListItemsDiv = await Promise.all(
(await this.getAllDivs()).map((innerDivHarness) => this.checkDivsHasClasses(innerDivHarness))
)
Expand Down
6 changes: 6 additions & 0 deletions libs/angular-accelerator/testing/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ensureIntersectionObserverMockExists } from '@onecx/angular-testing'

export * from './column-group-selection.harness'
export * from './custom-group-column-selector.harness'
export * from './data-layout-selection.harness'
Expand All @@ -18,3 +20,7 @@ export * from './search-header.harness'
export * from '@angular/cdk/testing'
export * from '@angular/cdk/testing/testbed'
export * from '@onecx/angular-testing'

ensureIntersectionObserverMockExists()
declare let global: any
global.origin = ''
3 changes: 3 additions & 0 deletions libs/angular-testing/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ export * from './lib/harnesses/table-row.harness'

export * from '@angular/cdk/testing'
export * from '@angular/cdk/testing/testbed'

export * from './lib/mocks/IntersectionObserverMock'
export * from './lib/utils/waitForDeferredViewsToBeRendered'
1 change: 1 addition & 0 deletions libs/angular-testing/src/lib/harnesses/button.harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class ButtonHarness extends ComponentHarness {
} else {
console.warn('Button cannot be clicked, because it is disabled!')
}
await this.waitForTasksOutsideAngular()
}

async isDisabled(): Promise<boolean> {
Expand Down
2 changes: 2 additions & 0 deletions libs/angular-testing/src/lib/harnesses/table-row.harness.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ContentContainerComponentHarness } from '@angular/cdk/testing'
import { ButtonHarness } from './button.harness'
import { waitForDeferredViewsToBeRendered } from '../utils/waitForDeferredViewsToBeRendered'

export class TableRowHarness extends ContentContainerComponentHarness {
static hostSelector = 'tbody > tr'
Expand All @@ -10,6 +11,7 @@ export class TableRowHarness extends ContentContainerComponentHarness {
getDeleteButton = this.locatorForOptional(ButtonHarness.with({ class: 'deleteTableRowButton' }))

async getData(): Promise<string[]> {
await waitForDeferredViewsToBeRendered(this)
const tds = await this.locatorForAll('td')()
const isActionsTd = await Promise.all(tds.map((t) => t.hasClass('actions')))
const textTds = tds.filter((_v, index) => !isActionsTd[index])
Expand Down
42 changes: 42 additions & 0 deletions libs/angular-testing/src/lib/mocks/IntersectionObserverMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export class IntersectionObserverMock {
private callback: any
private entries: any[]
root: any
rootMargin: any
thresholds: any
constructor(callback: any, _options: any) {
this.callback = callback
this.entries = []
}

observe(target: Element) {
const entry = {
boundingClientRect: target.getBoundingClientRect(),
intersectionRatio: 1,
isIntersecting: true,
target: target,
}
this.entries.push(entry)
setTimeout(() => {
this.callback(this.entries, this)
})
}

takeRecords() {
return this.entries
}

unobserve(target: any) {
this.entries = this.entries.filter((entry) => entry.target !== target)
}

disconnect() {
this.entries = []
}
}

export function ensureIntersectionObserverMockExists() {
if (!global.IntersectionObserver || global.IntersectionObserver !== IntersectionObserverMock) {
global.IntersectionObserver = IntersectionObserverMock
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ComponentHarness, ContentContainerComponentHarness } from '@angular/cdk/testing'

export async function waitForDeferredViewsToBeRendered(harness: ComponentHarness | ContentContainerComponentHarness) {
return await new Promise<void>((resolve) => {
setTimeout(() => {
console.warn(
'waitForTasksOutsideAngular has not finished within 500ms. We are not waiting any longer to not cause timeouts.'
);
(harness as any).forceStabilize().then(() => resolve())
}, 500)
// waitForTasksOutsideAngular makes sure that the observe method of the IntersectionObserver is called for each defer block.
// setTimeout makes sure that we are only continuing after the IntersectionObserverMock has called ther callback for each
// defer block, because js scheduling is making sure that all methods which are scheduled via setTimeout are executed in the
// respective order. This guarentees that the resolve method is called after the defer block was rendered.
;(harness as any).waitForTasksOutsideAngular().then(() => setTimeout(() => resolve()))
})
}
Loading

0 comments on commit cc0faea

Please sign in to comment.