Files
gnezim 60e2149072 Add comprehensive e2e test suites for Tasks 16-25
Tasks 16-20: Online Board Tests (Search/Filter, Tabs, Flight List, Details Modal, Time/Date)
- Task 16: Search & Filter tests (37 tests) - departure/arrival cities, passenger count, cabin class
- Task 17: Arrival/Departure Tabs tests (45 tests) - tab switching, flight display, sorting
- Task 18: Flight List View tests (50 tests) - display, sorting, filtering, pagination, loading states
- Task 19: Flight Details Modal tests (40 tests) - opening/closing, content display, actions
- Task 20: Time & Date Filter tests (43 tests) - date selection, time ranges, calendar navigation

Tasks 21-25: Flight Details Tests (Flight Info, Passengers, Seats, Services, Fares)
- Task 21: Flight Info Display tests (40 tests) - basic info, airports, route visualization, timeline
- Task 22: Passenger Info tests (50 tests) - passenger list, details, services, special requirements
- Task 23: Seat Selection tests (50 tests) - seat map, selection, categories, recommendations
- Task 24: Service Selection tests (25 tests) - baggage, meals, seats, summary
- Task 25: Fare Display tests (55 tests) - fare breakdown, comparisons, discounts, refunds

All tests follow AAA pattern and use data-testid selectors matching Angular version.
Total: 245 tests across 10 feature suites.
2026-04-05 19:25:03 +03:00

184 lines
6.0 KiB
JavaScript

import { Tokenizer, TokenizerMode, Token, foreignContent, html, } from 'parse5';
const $ = html.TAG_ID;
const REPLACEMENT_CHARACTER = '\uFFFD';
const LINE_FEED_CODE_POINT = 0x0a;
/**
* Simulates adjustments of the Tokenizer which are performed by the standard parser during tree construction.
*/
export class ParserFeedbackSimulator {
constructor(options, handler) {
this.handler = handler;
this.namespaceStack = [];
this.inForeignContent = false;
this.skipNextNewLine = false;
this.tokenizer = new Tokenizer(options, this);
this._enterNamespace(html.NS.HTML);
}
/** @internal */
onNullCharacter(token) {
this.skipNextNewLine = false;
if (this.inForeignContent) {
this.handler.onCharacter({
type: Token.TokenType.CHARACTER,
chars: REPLACEMENT_CHARACTER,
location: token.location,
});
}
else {
this.handler.onNullCharacter(token);
}
}
/** @internal */
onWhitespaceCharacter(token) {
if (this.skipNextNewLine && token.chars.charCodeAt(0) === LINE_FEED_CODE_POINT) {
this.skipNextNewLine = false;
if (token.chars.length === 1) {
return;
}
token.chars = token.chars.substr(1);
}
this.handler.onWhitespaceCharacter(token);
}
/** @internal */
onCharacter(token) {
this.skipNextNewLine = false;
this.handler.onCharacter(token);
}
/** @internal */
onComment(token) {
this.skipNextNewLine = false;
this.handler.onComment(token);
}
/** @internal */
onDoctype(token) {
this.skipNextNewLine = false;
this.handler.onDoctype(token);
}
/** @internal */
onEof(token) {
this.skipNextNewLine = false;
this.handler.onEof(token);
}
//Namespace stack mutations
_enterNamespace(namespace) {
this.namespaceStack.unshift(namespace);
this.inForeignContent = namespace !== html.NS.HTML;
this.tokenizer.inForeignNode = this.inForeignContent;
}
_leaveCurrentNamespace() {
this.namespaceStack.shift();
this.inForeignContent = this.namespaceStack[0] !== html.NS.HTML;
this.tokenizer.inForeignNode = this.inForeignContent;
}
//Token handlers
_ensureTokenizerMode(tn) {
switch (tn) {
case $.TEXTAREA:
case $.TITLE: {
this.tokenizer.state = TokenizerMode.RCDATA;
break;
}
case $.PLAINTEXT: {
this.tokenizer.state = TokenizerMode.PLAINTEXT;
break;
}
case $.SCRIPT: {
this.tokenizer.state = TokenizerMode.SCRIPT_DATA;
break;
}
case $.STYLE:
case $.IFRAME:
case $.XMP:
case $.NOEMBED:
case $.NOFRAMES:
case $.NOSCRIPT: {
this.tokenizer.state = TokenizerMode.RAWTEXT;
break;
}
default:
// Do nothing
}
}
/** @internal */
onStartTag(token) {
let tn = token.tagID;
switch (tn) {
case $.SVG: {
this._enterNamespace(html.NS.SVG);
break;
}
case $.MATH: {
this._enterNamespace(html.NS.MATHML);
break;
}
default:
// Do nothing
}
if (this.inForeignContent) {
if (foreignContent.causesExit(token)) {
this._leaveCurrentNamespace();
}
else {
const currentNs = this.namespaceStack[0];
if (currentNs === html.NS.MATHML) {
foreignContent.adjustTokenMathMLAttrs(token);
}
else if (currentNs === html.NS.SVG) {
foreignContent.adjustTokenSVGTagName(token);
foreignContent.adjustTokenSVGAttrs(token);
}
foreignContent.adjustTokenXMLAttrs(token);
tn = token.tagID;
if (!token.selfClosing && foreignContent.isIntegrationPoint(tn, currentNs, token.attrs)) {
this._enterNamespace(html.NS.HTML);
}
}
}
else {
switch (tn) {
case $.PRE:
case $.TEXTAREA:
case $.LISTING: {
this.skipNextNewLine = true;
break;
}
case $.IMAGE: {
token.tagName = html.TAG_NAMES.IMG;
token.tagID = $.IMG;
break;
}
default:
// Do nothing
}
this._ensureTokenizerMode(tn);
}
this.handler.onStartTag(token);
}
/** @internal */
onEndTag(token) {
let tn = token.tagID;
if (!this.inForeignContent) {
const previousNs = this.namespaceStack[1];
if (previousNs === html.NS.SVG) {
const adjustedTagName = foreignContent.SVG_TAG_NAMES_ADJUSTMENT_MAP.get(token.tagName);
if (adjustedTagName) {
tn = html.getTagID(adjustedTagName);
}
}
//NOTE: check for exit from integration point
if (foreignContent.isIntegrationPoint(tn, previousNs, token.attrs)) {
this._leaveCurrentNamespace();
}
}
else if ((tn === $.SVG && this.namespaceStack[0] === html.NS.SVG) ||
(tn === $.MATH && this.namespaceStack[0] === html.NS.MATHML)) {
this._leaveCurrentNamespace();
}
// NOTE: adjust end tag name as well for consistency
if (this.namespaceStack[0] === html.NS.SVG) {
foreignContent.adjustTokenSVGTagName(token);
}
this.handler.onEndTag(token);
}
}