testing.mjs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. import { ComponentHarness, HarnessPredicate, ContentContainerComponentHarness, parallel } from '@angular/cdk/testing';
  2. import { MatDividerHarness } from '../divider/testing.mjs';
  3. const iconSelector = '.mat-mdc-list-item-icon';
  4. const avatarSelector = '.mat-mdc-list-item-avatar';
  5. /**
  6. * Gets a `HarnessPredicate` that applies the given `BaseListItemHarnessFilters` to the given
  7. * list item harness.
  8. * @template H The type of list item harness to create a predicate for.
  9. * @param harnessType A constructor for a list item harness.
  10. * @param options An instance of `BaseListItemHarnessFilters` to apply.
  11. * @return A `HarnessPredicate` for the given harness type with the given options applied.
  12. */
  13. function getListItemPredicate(harnessType, options) {
  14. return new HarnessPredicate(harnessType, options)
  15. .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))
  16. .addOption('fullText', options.fullText, (harness, fullText) => HarnessPredicate.stringMatches(harness.getFullText(), fullText))
  17. .addOption('title', options.title, (harness, title) => HarnessPredicate.stringMatches(harness.getTitle(), title))
  18. .addOption('secondaryText', options.secondaryText, (harness, secondaryText) => HarnessPredicate.stringMatches(harness.getSecondaryText(), secondaryText))
  19. .addOption('tertiaryText', options.tertiaryText, (harness, tertiaryText) => HarnessPredicate.stringMatches(harness.getTertiaryText(), tertiaryText));
  20. }
  21. /** Harness for interacting with a list subheader. */
  22. class MatSubheaderHarness extends ComponentHarness {
  23. static hostSelector = '.mat-mdc-subheader';
  24. static with(options = {}) {
  25. return new HarnessPredicate(MatSubheaderHarness, options).addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
  26. }
  27. /** Gets the full text content of the list item (including text from any font icons). */
  28. async getText() {
  29. return (await this.host()).text();
  30. }
  31. }
  32. /** Selectors for the various list item sections that may contain user content. */
  33. var MatListItemSection;
  34. (function (MatListItemSection) {
  35. MatListItemSection["CONTENT"] = ".mdc-list-item__content";
  36. })(MatListItemSection || (MatListItemSection = {}));
  37. /** Enum describing the possible variants of a list item. */
  38. var MatListItemType;
  39. (function (MatListItemType) {
  40. MatListItemType[MatListItemType["ONE_LINE_ITEM"] = 0] = "ONE_LINE_ITEM";
  41. MatListItemType[MatListItemType["TWO_LINE_ITEM"] = 1] = "TWO_LINE_ITEM";
  42. MatListItemType[MatListItemType["THREE_LINE_ITEM"] = 2] = "THREE_LINE_ITEM";
  43. })(MatListItemType || (MatListItemType = {}));
  44. /**
  45. * Shared behavior among the harnesses for the various `MatListItem` flavors.
  46. * @docs-private
  47. */
  48. class MatListItemHarnessBase extends ContentContainerComponentHarness {
  49. _lines = this.locatorForAll('.mat-mdc-list-item-line');
  50. _primaryText = this.locatorFor('.mdc-list-item__primary-text');
  51. _avatar = this.locatorForOptional('.mat-mdc-list-item-avatar');
  52. _icon = this.locatorForOptional('.mat-mdc-list-item-icon');
  53. _unscopedTextContent = this.locatorFor('.mat-mdc-list-item-unscoped-content');
  54. /** Gets the type of the list item, currently describing how many lines there are. */
  55. async getType() {
  56. const host = await this.host();
  57. const [isOneLine, isTwoLine] = await parallel(() => [
  58. host.hasClass('mdc-list-item--with-one-line'),
  59. host.hasClass('mdc-list-item--with-two-lines'),
  60. ]);
  61. if (isOneLine) {
  62. return MatListItemType.ONE_LINE_ITEM;
  63. }
  64. else if (isTwoLine) {
  65. return MatListItemType.TWO_LINE_ITEM;
  66. }
  67. else {
  68. return MatListItemType.THREE_LINE_ITEM;
  69. }
  70. }
  71. /**
  72. * Gets the full text content of the list item, excluding text
  73. * from icons and avatars.
  74. *
  75. * @deprecated Use the `getFullText` method instead.
  76. * @breaking-change 16.0.0
  77. */
  78. async getText() {
  79. return this.getFullText();
  80. }
  81. /**
  82. * Gets the full text content of the list item, excluding text
  83. * from icons and avatars.
  84. */
  85. async getFullText() {
  86. return (await this.host()).text({ exclude: `${iconSelector}, ${avatarSelector}` });
  87. }
  88. /** Gets the title of the list item. */
  89. async getTitle() {
  90. return (await this._primaryText()).text();
  91. }
  92. /** Whether the list item is disabled. */
  93. async isDisabled() {
  94. return (await this.host()).hasClass('mdc-list-item--disabled');
  95. }
  96. /**
  97. * Gets the secondary line text of the list item. Null if the list item
  98. * does not have a secondary line.
  99. */
  100. async getSecondaryText() {
  101. const type = await this.getType();
  102. if (type === MatListItemType.ONE_LINE_ITEM) {
  103. return null;
  104. }
  105. const [lines, unscopedTextContent] = await parallel(() => [
  106. this._lines(),
  107. this._unscopedTextContent(),
  108. ]);
  109. // If there is no explicit line for the secondary text, the unscoped text content
  110. // is rendered as the secondary text (with potential text wrapping enabled).
  111. if (lines.length >= 1) {
  112. return lines[0].text();
  113. }
  114. else {
  115. return unscopedTextContent.text();
  116. }
  117. }
  118. /**
  119. * Gets the tertiary line text of the list item. Null if the list item
  120. * does not have a tertiary line.
  121. */
  122. async getTertiaryText() {
  123. const type = await this.getType();
  124. if (type !== MatListItemType.THREE_LINE_ITEM) {
  125. return null;
  126. }
  127. const [lines, unscopedTextContent] = await parallel(() => [
  128. this._lines(),
  129. this._unscopedTextContent(),
  130. ]);
  131. // First we check if there is an explicit line for the tertiary text. If so, we return it.
  132. // If there is at least an explicit secondary line though, then we know that the unscoped
  133. // text content corresponds to the tertiary line. If there are no explicit lines at all,
  134. // we know that the unscoped text content from the secondary text just wraps into the third
  135. // line, but there *no* actual dedicated tertiary text.
  136. if (lines.length === 2) {
  137. return lines[1].text();
  138. }
  139. else if (lines.length === 1) {
  140. return unscopedTextContent.text();
  141. }
  142. return null;
  143. }
  144. /** Whether this list item has an avatar. */
  145. async hasAvatar() {
  146. return !!(await this._avatar());
  147. }
  148. /** Whether this list item has an icon. */
  149. async hasIcon() {
  150. return !!(await this._icon());
  151. }
  152. }
  153. /**
  154. * Shared behavior among the harnesses for the various `MatList` flavors.
  155. * @template T A constructor type for a list item harness type used by this list harness.
  156. * @template C The list item harness type that `T` constructs.
  157. * @template F The filter type used filter list item harness of type `C`.
  158. * @docs-private
  159. */
  160. class MatListHarnessBase extends ComponentHarness {
  161. _itemHarness;
  162. /**
  163. * Gets a list of harnesses representing the items in this list.
  164. * @param filters Optional filters used to narrow which harnesses are included
  165. * @return The list of items matching the given filters.
  166. */
  167. async getItems(filters) {
  168. return this.locatorForAll(this._itemHarness.with(filters))();
  169. }
  170. /**
  171. * Gets a list of `ListSection` representing the list items grouped by subheaders. If the list has
  172. * no subheaders it is represented as a single `ListSection` with an undefined `heading` property.
  173. * @param filters Optional filters used to narrow which list item harnesses are included
  174. * @return The list of items matching the given filters, grouped into sections by subheader.
  175. */
  176. async getItemsGroupedBySubheader(filters) {
  177. const listSections = [];
  178. let currentSection = { items: [] };
  179. const itemsAndSubheaders = await this.getItemsWithSubheadersAndDividers({
  180. item: filters,
  181. divider: false,
  182. });
  183. for (const itemOrSubheader of itemsAndSubheaders) {
  184. if (itemOrSubheader instanceof MatSubheaderHarness) {
  185. if (currentSection.heading !== undefined || currentSection.items.length) {
  186. listSections.push(currentSection);
  187. }
  188. currentSection = { heading: itemOrSubheader.getText(), items: [] };
  189. }
  190. else {
  191. currentSection.items.push(itemOrSubheader);
  192. }
  193. }
  194. if (currentSection.heading !== undefined ||
  195. currentSection.items.length ||
  196. !listSections.length) {
  197. listSections.push(currentSection);
  198. }
  199. // Concurrently wait for all sections to resolve their heading if present.
  200. return parallel(() => listSections.map(async (s) => ({ items: s.items, heading: await s.heading })));
  201. }
  202. /**
  203. * Gets a list of sub-lists representing the list items grouped by dividers. If the list has no
  204. * dividers it is represented as a list with a single sub-list.
  205. * @param filters Optional filters used to narrow which list item harnesses are included
  206. * @return The list of items matching the given filters, grouped into sub-lists by divider.
  207. */
  208. async getItemsGroupedByDividers(filters) {
  209. const listSections = [[]];
  210. const itemsAndDividers = await this.getItemsWithSubheadersAndDividers({
  211. item: filters,
  212. subheader: false,
  213. });
  214. for (const itemOrDivider of itemsAndDividers) {
  215. if (itemOrDivider instanceof MatDividerHarness) {
  216. listSections.push([]);
  217. }
  218. else {
  219. listSections[listSections.length - 1].push(itemOrDivider);
  220. }
  221. }
  222. return listSections;
  223. }
  224. async getItemsWithSubheadersAndDividers(filters = {}) {
  225. const query = [];
  226. if (filters.item !== false) {
  227. query.push(this._itemHarness.with(filters.item || {}));
  228. }
  229. if (filters.subheader !== false) {
  230. query.push(MatSubheaderHarness.with(filters.subheader));
  231. }
  232. if (filters.divider !== false) {
  233. query.push(MatDividerHarness.with(filters.divider));
  234. }
  235. return this.locatorForAll(...query)();
  236. }
  237. }
  238. /** Harness for interacting with a action-list in tests. */
  239. class MatActionListHarness extends MatListHarnessBase {
  240. /** The selector for the host element of a `MatActionList` instance. */
  241. static hostSelector = '.mat-mdc-action-list';
  242. /**
  243. * Gets a `HarnessPredicate` that can be used to search for an action list with specific
  244. * attributes.
  245. * @param options Options for filtering which action list instances are considered a match.
  246. * @return a `HarnessPredicate` configured with the given options.
  247. */
  248. static with(options = {}) {
  249. return new HarnessPredicate(this, options);
  250. }
  251. _itemHarness = MatActionListItemHarness;
  252. }
  253. /** Harness for interacting with an action list item. */
  254. class MatActionListItemHarness extends MatListItemHarnessBase {
  255. /** The selector for the host element of a `MatListItem` instance. */
  256. static hostSelector = `${MatActionListHarness.hostSelector} .mat-mdc-list-item`;
  257. /**
  258. * Gets a `HarnessPredicate` that can be used to search for a list item with specific
  259. * attributes.
  260. * @param options Options for filtering which action list item instances are considered a match.
  261. * @return a `HarnessPredicate` configured with the given options.
  262. */
  263. static with(options = {}) {
  264. return getListItemPredicate(this, options);
  265. }
  266. /** Clicks on the action list item. */
  267. async click() {
  268. return (await this.host()).click();
  269. }
  270. /** Focuses the action list item. */
  271. async focus() {
  272. return (await this.host()).focus();
  273. }
  274. /** Blurs the action list item. */
  275. async blur() {
  276. return (await this.host()).blur();
  277. }
  278. /** Whether the action list item is focused. */
  279. async isFocused() {
  280. return (await this.host()).isFocused();
  281. }
  282. }
  283. /** Harness for interacting with a list in tests. */
  284. class MatListHarness extends MatListHarnessBase {
  285. /** The selector for the host element of a `MatList` instance. */
  286. static hostSelector = '.mat-mdc-list';
  287. /**
  288. * Gets a `HarnessPredicate` that can be used to search for a list with specific attributes.
  289. * @param options Options for filtering which list instances are considered a match.
  290. * @return a `HarnessPredicate` configured with the given options.
  291. */
  292. static with(options = {}) {
  293. return new HarnessPredicate(this, options);
  294. }
  295. _itemHarness = MatListItemHarness;
  296. }
  297. /** Harness for interacting with a list item. */
  298. class MatListItemHarness extends MatListItemHarnessBase {
  299. /** The selector for the host element of a `MatListItem` instance. */
  300. static hostSelector = `${MatListHarness.hostSelector} .mat-mdc-list-item`;
  301. /**
  302. * Gets a `HarnessPredicate` that can be used to search for a list item with specific attributes.
  303. * @param options Options for filtering which list item instances are considered a match.
  304. * @return a `HarnessPredicate` configured with the given options.
  305. */
  306. static with(options = {}) {
  307. return getListItemPredicate(this, options);
  308. }
  309. }
  310. /** Harness for interacting with a mat-nav-list in tests. */
  311. class MatNavListHarness extends MatListHarnessBase {
  312. /** The selector for the host element of a `MatNavList` instance. */
  313. static hostSelector = '.mat-mdc-nav-list';
  314. /**
  315. * Gets a `HarnessPredicate` that can be used to search for a nav list with specific
  316. * attributes.
  317. * @param options Options for filtering which nav list instances are considered a match.
  318. * @return a `HarnessPredicate` configured with the given options.
  319. */
  320. static with(options = {}) {
  321. return new HarnessPredicate(this, options);
  322. }
  323. _itemHarness = MatNavListItemHarness;
  324. }
  325. /** Harness for interacting with a nav-list item. */
  326. class MatNavListItemHarness extends MatListItemHarnessBase {
  327. /** The selector for the host element of a `MatListItem` instance. */
  328. static hostSelector = `${MatNavListHarness.hostSelector} .mat-mdc-list-item`;
  329. /**
  330. * Gets a `HarnessPredicate` that can be used to search for a nav list item with specific
  331. * attributes.
  332. * @param options Options for filtering which nav list item instances are considered a match.
  333. * @return a `HarnessPredicate` configured with the given options.
  334. */
  335. static with(options = {}) {
  336. return getListItemPredicate(this, options)
  337. .addOption('href', options.href, async (harness, href) => HarnessPredicate.stringMatches(harness.getHref(), href))
  338. .addOption('activated', options.activated, async (harness, activated) => (await harness.isActivated()) === activated);
  339. }
  340. /** Gets the href for this nav list item. */
  341. async getHref() {
  342. return (await this.host()).getAttribute('href');
  343. }
  344. /** Clicks on the nav list item. */
  345. async click() {
  346. return (await this.host()).click();
  347. }
  348. /** Focuses the nav list item. */
  349. async focus() {
  350. return (await this.host()).focus();
  351. }
  352. /** Blurs the nav list item. */
  353. async blur() {
  354. return (await this.host()).blur();
  355. }
  356. /** Whether the nav list item is focused. */
  357. async isFocused() {
  358. return (await this.host()).isFocused();
  359. }
  360. /** Whether the list item is activated. Should only be used for nav list items. */
  361. async isActivated() {
  362. return (await this.host()).hasClass('mdc-list-item--activated');
  363. }
  364. }
  365. /** Harness for interacting with a selection-list in tests. */
  366. class MatSelectionListHarness extends MatListHarnessBase {
  367. /** The selector for the host element of a `MatSelectionList` instance. */
  368. static hostSelector = '.mat-mdc-selection-list';
  369. /**
  370. * Gets a `HarnessPredicate` that can be used to search for a selection list with specific
  371. * attributes.
  372. * @param options Options for filtering which selection list instances are considered a match.
  373. * @return a `HarnessPredicate` configured with the given options.
  374. */
  375. static with(options = {}) {
  376. return new HarnessPredicate(this, options);
  377. }
  378. _itemHarness = MatListOptionHarness;
  379. /** Whether the selection list is disabled. */
  380. async isDisabled() {
  381. return (await (await this.host()).getAttribute('aria-disabled')) === 'true';
  382. }
  383. /**
  384. * Selects all items matching any of the given filters.
  385. * @param filters Filters that specify which items should be selected.
  386. */
  387. async selectItems(...filters) {
  388. const items = await this._getItems(filters);
  389. await parallel(() => items.map(item => item.select()));
  390. }
  391. /**
  392. * Deselects all items matching any of the given filters.
  393. * @param filters Filters that specify which items should be deselected.
  394. */
  395. async deselectItems(...filters) {
  396. const items = await this._getItems(filters);
  397. await parallel(() => items.map(item => item.deselect()));
  398. }
  399. /** Gets all items matching the given list of filters. */
  400. async _getItems(filters) {
  401. if (!filters.length) {
  402. return this.getItems();
  403. }
  404. const matches = await parallel(() => filters.map(filter => this.locatorForAll(MatListOptionHarness.with(filter))()));
  405. return matches.reduce((result, current) => [...result, ...current], []);
  406. }
  407. }
  408. /** Harness for interacting with a list option. */
  409. class MatListOptionHarness extends MatListItemHarnessBase {
  410. /** The selector for the host element of a `MatListOption` instance. */
  411. static hostSelector = '.mat-mdc-list-option';
  412. /**
  413. * Gets a `HarnessPredicate` that can be used to search for a list option with specific
  414. * attributes.
  415. * @param options Options for filtering which list option instances are considered a match.
  416. * @return a `HarnessPredicate` configured with the given options.
  417. */
  418. static with(options = {}) {
  419. return getListItemPredicate(this, options).addOption('is selected', options.selected, async (harness, selected) => (await harness.isSelected()) === selected);
  420. }
  421. _beforeCheckbox = this.locatorForOptional('.mdc-list-item__start .mdc-checkbox');
  422. _beforeRadio = this.locatorForOptional('.mdc-list-item__start .mdc-radio');
  423. /** Gets the position of the checkbox relative to the list option content. */
  424. async getCheckboxPosition() {
  425. return (await this._beforeCheckbox()) !== null ? 'before' : 'after';
  426. }
  427. /** Gets the position of the radio relative to the list option content. */
  428. async getRadioPosition() {
  429. return (await this._beforeRadio()) !== null ? 'before' : 'after';
  430. }
  431. /** Whether the list option is selected. */
  432. async isSelected() {
  433. return (await (await this.host()).getAttribute('aria-selected')) === 'true';
  434. }
  435. /** Focuses the list option. */
  436. async focus() {
  437. return (await this.host()).focus();
  438. }
  439. /** Blurs the list option. */
  440. async blur() {
  441. return (await this.host()).blur();
  442. }
  443. /** Whether the list option is focused. */
  444. async isFocused() {
  445. return (await this.host()).isFocused();
  446. }
  447. /** Toggles the checked state of the checkbox. */
  448. async toggle() {
  449. return (await this.host()).click();
  450. }
  451. /**
  452. * Puts the list option in a checked state by toggling it if it is currently
  453. * unchecked, or doing nothing if it is already checked.
  454. */
  455. async select() {
  456. if (!(await this.isSelected())) {
  457. return this.toggle();
  458. }
  459. }
  460. /**
  461. * Puts the list option in an unchecked state by toggling it if it is currently
  462. * checked, or doing nothing if it is already unchecked.
  463. */
  464. async deselect() {
  465. if (await this.isSelected()) {
  466. return this.toggle();
  467. }
  468. }
  469. }
  470. export { MatActionListHarness, MatActionListItemHarness, MatListHarness, MatListItemHarness, MatListItemSection, MatListItemType, MatListOptionHarness, MatNavListHarness, MatNavListItemHarness, MatSelectionListHarness, MatSubheaderHarness };
  471. //# sourceMappingURL=testing.mjs.map