|
1 | 1 | import * as TKUnit from '../../tk-unit'; |
2 | 2 | import * as helper from '../../ui-helper'; |
3 | 3 | import { UITest } from '../../ui-test'; |
4 | | -import { isAndroid, Page, View, KeyedTemplate, Utils, Observable, EventData, ObservableArray, Label, Application, ListView, ItemEventData } from '@nativescript/core'; |
| 4 | +import { isAndroid, Page, View, KeyedTemplate, Utils, Observable, EventData, ObservableArray, Label, Application, ListView, ItemEventData, StackLayout } from '@nativescript/core'; |
5 | 5 | import { MyButton, MyStackLayout } from '../layouts/layout-helper'; |
6 | 6 |
|
7 | 7 | // >> article-item-tap |
@@ -369,9 +369,10 @@ export class ListViewTest extends UITest<ListView> { |
369 | 369 | } |
370 | 370 |
|
371 | 371 | public test_loadMoreItems_raised_when_showing_few_items() { |
372 | | - var listView = this.testView; |
| 372 | + this.setUp(); |
| 373 | + const listView = this.testView; |
373 | 374 |
|
374 | | - var loadMoreItemsCount = 0; |
| 375 | + let loadMoreItemsCount = 0; |
375 | 376 | listView.items = FEW_ITEMS; |
376 | 377 | listView.on(ListView.itemLoadingEvent, this.loadViewWithItemNumber); |
377 | 378 | // >> article-loadmoreitems-event |
@@ -751,10 +752,136 @@ export class ListViewTest extends UITest<ListView> { |
751 | 752 | listView.scrollToIndex(10000); |
752 | 753 | } |
753 | 754 |
|
| 755 | + // Sticky header sanity tests |
| 756 | + public test_stickyHeader_iOS_sectioned_headers_basic() { |
| 757 | + if (!__APPLE__) { |
| 758 | + return; |
| 759 | + } |
| 760 | + |
| 761 | + this.setUp(); |
| 762 | + |
| 763 | + const listView = this.testView; |
| 764 | + listView.sectioned = true; |
| 765 | + listView.stickyHeader = true; |
| 766 | + listView.stickyHeaderTemplate = "<Label id='headerLabel' text='{{ title }}' />"; |
| 767 | + |
| 768 | + const items = [ |
| 769 | + { title: 'Section A', items: [1, 2, 3] }, |
| 770 | + { title: 'Section B', items: [4, 5] }, |
| 771 | + ]; |
| 772 | + listView.items = items; |
| 773 | + |
| 774 | + // Ensure layout |
| 775 | + this.waitUntilTestElementIsLoaded(); |
| 776 | + this.waitUntilTestElementLayoutIsValid(); |
| 777 | + |
| 778 | + const table = <UITableView>listView.ios; |
| 779 | + TKUnit.assertEqual(table.numberOfSections, 2, 'iOS sticky headers should use sections'); |
| 780 | + |
| 781 | + // Default auto height is ~44; ensure > 0 |
| 782 | + const rect0 = table.rectForHeaderInSection(0); |
| 783 | + TKUnit.assert(rect0.size.height > 0, 'header height > 0'); |
| 784 | + |
| 785 | + // Template binding sanity: force-create header view via delegate and read label text |
| 786 | + const header0 = (<any>table.delegate).tableViewViewForHeaderInSection(table, 0); |
| 787 | + const headerText0 = this.getTextFromNativeHeaderForSection(listView, header0); |
| 788 | + TKUnit.assertEqual(headerText0, 'Section A', 'iOS header 0 text'); |
| 789 | + |
| 790 | + // Respect explicit stickyHeaderHeight |
| 791 | + listView.stickyHeaderHeight = 60; |
| 792 | + listView.refresh(); |
| 793 | + TKUnit.wait(0.05); |
| 794 | + const rect0b = table.rectForHeaderInSection(0); |
| 795 | + // iOS reports in points; allow small variance |
| 796 | + TKUnit.assert(Math.abs(rect0b.size.height - 60) <= 1, 'explicit header height ~60'); |
| 797 | + } |
| 798 | + |
| 799 | + public test_stickyHeader_Android_header_updates_and_padding() { |
| 800 | + if (!isAndroid) { |
| 801 | + return; |
| 802 | + } |
| 803 | + |
| 804 | + this.setUp(); |
| 805 | + |
| 806 | + const listView = this.testView; |
| 807 | + listView.sectioned = true; |
| 808 | + listView.stickyHeader = true; |
| 809 | + listView.stickyHeaderTemplate = "<Label id='headerLabel' text='{{ title }}' />"; |
| 810 | + listView.items = [ |
| 811 | + { title: 'First', items: ['a', 'b', 'c'] }, |
| 812 | + { title: 'Second', items: ['d', 'e'] }, |
| 813 | + ]; |
| 814 | + |
| 815 | + this.waitUntilTestElementIsLoaded(); |
| 816 | + this.waitUntilTestElementLayoutIsValid(); |
| 817 | + TKUnit.waitUntilReady(() => !!(<any>listView)._stickyHeaderView); |
| 818 | + |
| 819 | + // Sticky header view exists and binds |
| 820 | + const sticky = (<any>listView)._stickyHeaderView; |
| 821 | + TKUnit.assert(!!sticky, 'sticky header view exists'); |
| 822 | + const text0 = this.getStickyHeaderTextAndroid(listView); |
| 823 | + TKUnit.assertEqual(text0, 'First', 'Android sticky header initial text'); |
| 824 | + |
| 825 | + // ListView should have top padding to avoid content under header |
| 826 | + const topPad = (<android.widget.ListView>listView.android).getPaddingTop(); |
| 827 | + TKUnit.assert(topPad > 0, 'ListView has top padding for sticky header'); |
| 828 | + |
| 829 | + // Update header to next section (simulate scroll) |
| 830 | + if ((<any>listView)._updateStickyHeader) { |
| 831 | + (<any>listView)._updateStickyHeader(1); |
| 832 | + TKUnit.wait(0.05); |
| 833 | + const text1 = this.getStickyHeaderTextAndroid(listView); |
| 834 | + TKUnit.assertEqual(text1, 'Second', 'Android sticky header updated text'); |
| 835 | + } |
| 836 | + } |
| 837 | + |
754 | 838 | private checkItemVisibleAtIndex(listView: ListView, index: number): boolean { |
755 | 839 | return listView.isItemAtIndexVisible(index); |
756 | 840 | } |
757 | 841 |
|
| 842 | + private getTextFromNativeHeaderForSection(listView: ListView, headerView: any): string { |
| 843 | + if (__APPLE__ && headerView && headerView.contentView && headerView.contentView.subviews) { |
| 844 | + // subviews can be function or array-like depending on runtime bridge |
| 845 | + try { |
| 846 | + if (Utils.isFunction(headerView.contentView.subviews)) { |
| 847 | + const sv = headerView.contentView.subviews(); |
| 848 | + return sv && sv.length ? sv[0].text + '' : ''; |
| 849 | + } else { |
| 850 | + return headerView.contentView.subviews[0].text + ''; |
| 851 | + } |
| 852 | + } catch (e) { |
| 853 | + return ''; |
| 854 | + } |
| 855 | + } |
| 856 | + |
| 857 | + return ''; |
| 858 | + } |
| 859 | + |
| 860 | + private getStickyHeaderTextAndroid(listView: ListView): string { |
| 861 | + if (isAndroid) { |
| 862 | + const headerView = (<any>listView)._stickyHeaderView as StackLayout; |
| 863 | + if (!headerView) { |
| 864 | + return ''; |
| 865 | + } |
| 866 | + if (headerView instanceof Label) { |
| 867 | + return headerView.text + ''; |
| 868 | + } |
| 869 | + if (headerView.getChildAt) { |
| 870 | + const child = headerView.getChildAt(0) as StackLayout; |
| 871 | + if (child instanceof Label) { |
| 872 | + return child.text + ''; |
| 873 | + } |
| 874 | + if (child?.getChildAt) { |
| 875 | + const gchild = child.getChildAt(0); |
| 876 | + if (gchild instanceof Label) { |
| 877 | + return gchild.text + ''; |
| 878 | + } |
| 879 | + } |
| 880 | + } |
| 881 | + } |
| 882 | + return ''; |
| 883 | + } |
| 884 | + |
758 | 885 | private assertNoMemoryLeak(weakRef: WeakRef<ListView>) { |
759 | 886 | this.tearDown(); |
760 | 887 | // |
|
0 commit comments