Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions apps/automated/src/ui/list-view/list-view-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,69 @@ export class ListViewTest extends UITest<ListView> {
}
}

// Sectioned ListView + multiple item templates tests (fix for #11133)
public test_SectionedListView_ItemTemplateSelector_CorrectTemplatePerSection() {
// Verifies that _getItemTemplateInSection resolves the template using the
// actual row data item (not the section wrapper object).
const listView = this.testView;
listView.sectioned = true;

// Section 0 items have age=0 (even → 'red'), section 1 items have age=1 (odd → 'green')
listView.items = [
{ title: 'Section A', items: [{ age: 0 }, { age: 2 }] },
{ title: 'Section B', items: [{ age: 1 }, { age: 3 }] },
];
listView.itemTemplates = this._itemTemplatesString;
listView.itemTemplateSelector = (item: any) => (item.age % 2 === 0 ? 'red' : 'green');

// Section 0, row 0: age=0 → 'red'
const template00 = listView._getItemTemplateInSection(0, 0);
TKUnit.assertEqual(template00.key, 'red', 'section 0 row 0 should use red template');

// Section 0, row 1: age=2 → 'red'
const template01 = listView._getItemTemplateInSection(0, 1);
TKUnit.assertEqual(template01.key, 'red', 'section 0 row 1 should use red template');

// Section 1, row 0: age=1 → 'green'
const template10 = listView._getItemTemplateInSection(1, 0);
TKUnit.assertEqual(template10.key, 'green', 'section 1 row 0 should use green template');

// Section 1, row 1: age=3 → 'green'
const template11 = listView._getItemTemplateInSection(1, 1);
TKUnit.assertEqual(template11.key, 'green', 'section 1 row 1 should use green template');
}

public test_SectionedListView_ItemTemplateSelector_DifferentTemplatesWithinSameSection() {
// Verifies mixed templates within a single section are resolved correctly.
const listView = this.testView;
listView.sectioned = true;

listView.items = [{ title: 'Mixed', items: [{ age: 0 }, { age: 1 }, { age: 2 }] }];
listView.itemTemplates = this._itemTemplatesString;
listView.itemTemplateSelector = (item: any) => (item.age % 2 === 0 ? 'red' : 'green');

TKUnit.assertEqual(listView._getItemTemplateInSection(0, 0).key, 'red', 'row 0 (age=0) → red');
TKUnit.assertEqual(listView._getItemTemplateInSection(0, 1).key, 'green', 'row 1 (age=1) → green');
TKUnit.assertEqual(listView._getItemTemplateInSection(0, 2).key, 'red', 'row 2 (age=2) → red');
}

public test_SectionedListView_ItemTemplateSelector_UnknownKeyFallsBackToDefault() {
// Mirrors test_ItemTemplateSelector_WhenWrongTemplateKeyIsSpecified_TheDefaultTemplateIsUsed
// but for the sectioned path.
const listView = this.testView;
listView.sectioned = true;

listView.items = [{ title: 'Section A', items: [{ age: 0 }] }];
listView.itemTemplate = "<Label text='default' />";
listView.itemTemplates = this._itemTemplatesString;
// Selector always returns a key that does not exist in _itemTemplatesInternal
listView.itemTemplateSelector = (_item: any) => 'nonexistent';

const template = listView._getItemTemplateInSection(0, 0);
// Should fall back to the first (default) template
TKUnit.assertEqual(template.key, 'default', 'unknown template key should fall back to default');
}

private _itemTemplatesString = `
<template key="red">
<Label text='red' style.backgroundColor='red' minHeight='100' maxHeight='100'/>
Expand Down
8 changes: 4 additions & 4 deletions packages/core/ui/list-view/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,8 +996,8 @@ function ensureListViewAdapterClass() {
// Header view type is the last index (after all item template types)
return this.owner._itemTemplatesInternal.length;
} else {
// Get template for the actual item
const template = this.owner._getItemTemplate(positionInfo.itemIndex);
// Get template for the actual item using section-aware lookup
const template = this.owner._getItemTemplateInSection(positionInfo.section, positionInfo.itemIndex);
return this.owner._itemTemplatesInternal.indexOf(template);
}
} else {
Expand Down Expand Up @@ -1124,8 +1124,8 @@ function ensureListViewAdapterClass() {
}

private _createItemView(section: number, itemIndex: number, convertView: android.view.View, parent: android.view.ViewGroup): android.view.View {
// Use existing item creation logic but with sectioned data
const template = this.owner._getItemTemplate(itemIndex);
// Use section-aware template lookup when in sectioned mode, flat lookup otherwise
const template = section >= 0 && this.owner.sectioned ? this.owner._getItemTemplateInSection(section, itemIndex) : this.owner._getItemTemplate(itemIndex);
let view: View;

// convertView is of the wrong type
Expand Down
12 changes: 3 additions & 9 deletions packages/core/ui/list-view/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class DataSource extends NSObject implements UITableViewDataSource {
const owner = this._owner?.deref();
let cell: ListViewCell;
if (owner) {
const template = owner._getItemTemplate(indexPath.row);
const template = owner.sectioned ? owner._getItemTemplateInSection(indexPath.section, indexPath.row) : owner._getItemTemplate(indexPath.row);
cell = <ListViewCell>(tableView.dequeueReusableCellWithIdentifier(template.key) || ListViewCell.initWithEmptyBackground());
owner._prepareCell(cell, indexPath);

Expand Down Expand Up @@ -235,7 +235,7 @@ class UITableViewDelegateImpl extends NSObject implements UITableViewDelegate {
let height = owner.getHeight(indexPath.row);
if (height === undefined) {
// in iOS8+ after call to scrollToRowAtIndexPath:atScrollPosition:animated: this method is called before tableViewCellForRowAtIndexPath so we need fake cell to measure its content.
const template = owner._getItemTemplate(indexPath.row);
const template = owner.sectioned ? owner._getItemTemplateInSection(indexPath.section, indexPath.row) : owner._getItemTemplate(indexPath.row);
let cell = this._measureCellMap.get(template.key);
if (!cell) {
cell = <any>tableView.dequeueReusableCellWithIdentifier(template.key) || ListViewCell.initWithEmptyBackground();
Expand Down Expand Up @@ -794,13 +794,7 @@ export class ListView extends ListViewBase {
let view: ItemView = cell.view;
if (!view) {
if (this.sectioned) {
// For sectioned data, we need to calculate the absolute index for template selection
let absoluteIndex = 0;
for (let i = 0; i < indexPath.section; i++) {
absoluteIndex += this._getItemsInSection(i).length;
}
absoluteIndex += indexPath.row;
view = this._getItemTemplate(absoluteIndex).createView();
view = this._getItemTemplateInSection(indexPath.section, indexPath.row).createView();
} else {
view = this._getItemTemplate(indexPath.row).createView();
}
Expand Down
18 changes: 18 additions & 0 deletions packages/core/ui/list-view/list-view-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,24 @@ export abstract class ListViewBase extends ContainerView implements ListViewDefi
return this._itemTemplatesInternal[0];
}

public _getItemTemplateInSection(section: number, index: number): KeyedTemplate {
let templateKey = 'default';
if (this.itemTemplateSelector) {
const dataItem = this._getDataItemInSection(section, index);
const sectionItems = this._getItemsInSection(section);
templateKey = this._itemTemplateSelector(dataItem, index, sectionItems);
}

for (let i = 0, length = this._itemTemplatesInternal.length; i < length; i++) {
if (this._itemTemplatesInternal[i].key === templateKey) {
return this._itemTemplatesInternal[i];
}
}

// This is the default template
return this._itemTemplatesInternal[0];
}

public _prepareItem(item: View, index: number) {
if (item) {
item.bindingContext = this._getDataItem(index);
Expand Down
Loading