60e2149072
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.
368 lines
7.8 KiB
JavaScript
368 lines
7.8 KiB
JavaScript
const errors = require('./lib/errors')
|
|
|
|
class EventListener {
|
|
constructor() {
|
|
this.list = []
|
|
this.count = 0
|
|
}
|
|
|
|
append(ctx, name, fn, once) {
|
|
this.count++
|
|
ctx.emit('newListener', name, fn) // Emit BEFORE adding
|
|
this.list.push([fn, once])
|
|
}
|
|
|
|
prepend(ctx, name, fn, once) {
|
|
this.count++
|
|
ctx.emit('newListener', name, fn) // Emit BEFORE adding
|
|
this.list.unshift([fn, once])
|
|
}
|
|
|
|
remove(ctx, name, fn) {
|
|
for (let i = 0, n = this.list.length; i < n; i++) {
|
|
const l = this.list[i]
|
|
|
|
if (l[0] === fn) {
|
|
this.list.splice(i, 1)
|
|
|
|
if (this.count === 1) delete ctx._events[name]
|
|
|
|
ctx.emit('removeListener', name, fn) // Emit AFTER removing
|
|
|
|
this.count--
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
removeAll(ctx, name) {
|
|
const list = [...this.list]
|
|
this.list = []
|
|
|
|
if (this.count === list.length) delete ctx._events[name]
|
|
|
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
ctx.emit('removeListener', name, list[i][0]) // Emit AFTER removing
|
|
}
|
|
|
|
this.count -= list.length
|
|
}
|
|
|
|
emit(ctx, name, ...args) {
|
|
const list = [...this.list]
|
|
|
|
for (let i = 0, n = list.length; i < n; i++) {
|
|
const l = list[i]
|
|
|
|
if (l[1] === true) this.remove(ctx, name, l[0])
|
|
|
|
Reflect.apply(l[0], ctx, args)
|
|
}
|
|
|
|
return list.length > 0
|
|
}
|
|
}
|
|
|
|
function appendListener(ctx, name, fn, once) {
|
|
if (ctx._events === undefined) ctx._events = Object.create(null)
|
|
const e = ctx._events[name] || (ctx._events[name] = new EventListener())
|
|
e.append(ctx, name, fn, once)
|
|
return ctx
|
|
}
|
|
|
|
function prependListener(ctx, name, fn, once) {
|
|
if (ctx._events === undefined) ctx._events = Object.create(null)
|
|
const e = ctx._events[name] || (ctx._events[name] = new EventListener())
|
|
e.prepend(ctx, name, fn, once)
|
|
return ctx
|
|
}
|
|
|
|
function removeListener(ctx, name, fn) {
|
|
if (ctx._events === undefined) return ctx
|
|
const e = ctx._events[name]
|
|
if (e !== undefined) e.remove(ctx, name, fn)
|
|
return ctx
|
|
}
|
|
|
|
function throwUnhandledError(...args) {
|
|
let err
|
|
|
|
if (args.length > 0) err = args[0]
|
|
|
|
if (err instanceof Error === false) err = errors.UNHANDLED_ERROR(err)
|
|
|
|
if (Error.captureStackTrace) {
|
|
Error.captureStackTrace(err, exports.prototype.emit)
|
|
}
|
|
|
|
queueMicrotask(() => {
|
|
throw err
|
|
})
|
|
}
|
|
|
|
module.exports = exports = class EventEmitter {
|
|
constructor() {
|
|
this._events = Object.create(null)
|
|
}
|
|
|
|
addListener(name, fn) {
|
|
return appendListener(this, name, fn, false)
|
|
}
|
|
|
|
addOnceListener(name, fn) {
|
|
return appendListener(this, name, fn, true)
|
|
}
|
|
|
|
prependListener(name, fn) {
|
|
return prependListener(this, name, fn, false)
|
|
}
|
|
|
|
prependOnceListener(name, fn) {
|
|
return prependListener(this, name, fn, true)
|
|
}
|
|
|
|
removeListener(name, fn) {
|
|
return removeListener(this, name, fn)
|
|
}
|
|
|
|
on(name, fn) {
|
|
return appendListener(this, name, fn, false)
|
|
}
|
|
|
|
once(name, fn) {
|
|
return appendListener(this, name, fn, true)
|
|
}
|
|
|
|
off(name, fn) {
|
|
return removeListener(this, name, fn)
|
|
}
|
|
|
|
emit(name, ...args) {
|
|
if (name === 'error' && this._events !== undefined && this._events.error === undefined) {
|
|
throwUnhandledError(...args)
|
|
}
|
|
|
|
if (this._events === undefined) return false
|
|
const e = this._events[name]
|
|
return e === undefined ? false : e.emit(this, name, ...args)
|
|
}
|
|
|
|
listeners(name) {
|
|
if (this._events === undefined) return []
|
|
const e = this._events[name]
|
|
return e === undefined ? [] : [...e.list]
|
|
}
|
|
|
|
listenerCount(name) {
|
|
if (this._events === undefined) return 0
|
|
const e = this._events[name]
|
|
return e === undefined ? 0 : e.list.length
|
|
}
|
|
|
|
getMaxListeners() {
|
|
return EventEmitter.defaultMaxListeners
|
|
}
|
|
|
|
setMaxListeners(n) {}
|
|
|
|
removeAllListeners(name) {
|
|
if (arguments.length === 0) {
|
|
for (const key of Reflect.ownKeys(this._events)) {
|
|
if (key === 'removeListener') continue
|
|
this.removeAllListeners(key)
|
|
}
|
|
this.removeAllListeners('removeListener')
|
|
} else {
|
|
const e = this._events[name]
|
|
if (e !== undefined) e.removeAll(this, name)
|
|
}
|
|
return this
|
|
}
|
|
}
|
|
|
|
exports.EventEmitter = exports
|
|
|
|
exports.errors = errors
|
|
|
|
exports.defaultMaxListeners = 10
|
|
|
|
exports.on = function on(emitter, name, opts = {}) {
|
|
const { signal } = opts
|
|
|
|
if (signal && signal.aborted) {
|
|
throw errors.OPERATION_ABORTED(signal.reason)
|
|
}
|
|
|
|
let error = null
|
|
let done = false
|
|
|
|
const events = []
|
|
const promises = []
|
|
|
|
if (name !== 'error') emitter.on('error', onerror)
|
|
|
|
if (signal) signal.addEventListener('abort', onabort)
|
|
|
|
emitter.on(name, onevent)
|
|
|
|
return {
|
|
next() {
|
|
if (events.length) {
|
|
return Promise.resolve({ value: events.shift(), done: false })
|
|
}
|
|
|
|
if (error) {
|
|
const err = error
|
|
|
|
error = null
|
|
|
|
return Promise.reject(err)
|
|
}
|
|
|
|
if (done) return onclose()
|
|
|
|
return new Promise((resolve, reject) => promises.push({ resolve, reject }))
|
|
},
|
|
|
|
return() {
|
|
return onclose()
|
|
},
|
|
|
|
throw(err) {
|
|
return onerror(err)
|
|
},
|
|
|
|
[Symbol.asyncIterator]() {
|
|
return this
|
|
}
|
|
}
|
|
|
|
function onevent(...args) {
|
|
if (promises.length) {
|
|
promises.shift().resolve({ value: args, done: false })
|
|
} else {
|
|
events.push(args)
|
|
}
|
|
}
|
|
|
|
function onerror(err) {
|
|
emitter.off(name, onevent).off('error', onerror)
|
|
|
|
if (promises.length) {
|
|
promises.shift().reject(err)
|
|
} else {
|
|
error = err
|
|
}
|
|
|
|
return Promise.resolve({ done: true })
|
|
}
|
|
|
|
function onabort() {
|
|
signal.removeEventListener('abort', onabort)
|
|
|
|
onerror(errors.OPERATION_ABORTED(signal.reason))
|
|
}
|
|
|
|
function onclose() {
|
|
emitter.off(name, onevent)
|
|
|
|
if (name !== 'error') emitter.off('error', onerror)
|
|
|
|
if (signal) signal.removeEventListener('abort', onabort)
|
|
|
|
done = true
|
|
|
|
if (promises.length) promises.shift().resolve({ done: true })
|
|
|
|
return Promise.resolve({ done: true })
|
|
}
|
|
}
|
|
|
|
exports.once = function once(emitter, name, opts = {}) {
|
|
const { signal } = opts
|
|
|
|
if (signal && signal.aborted) {
|
|
return Promise.reject(errors.OPERATION_ABORTED(signal.reason))
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
if (name !== 'error') emitter.on('error', onerror)
|
|
|
|
if (signal) signal.addEventListener('abort', onabort)
|
|
|
|
emitter.once(name, onevent)
|
|
|
|
function onevent(...args) {
|
|
if (name !== 'error') emitter.off('error', onerror)
|
|
|
|
if (signal) signal.removeEventListener('abort', onabort)
|
|
|
|
resolve(args)
|
|
}
|
|
|
|
function onerror(err) {
|
|
emitter.off(name, onevent)
|
|
|
|
if (name !== 'error') emitter.off('error', onerror)
|
|
|
|
reject(err)
|
|
}
|
|
|
|
function onabort() {
|
|
signal.removeEventListener('abort', onabort)
|
|
|
|
onerror(errors.OPERATION_ABORTED(signal.reason))
|
|
}
|
|
})
|
|
}
|
|
|
|
exports.forward = function forward(from, to, names, opts = {}) {
|
|
if (typeof names === 'string') names = [names]
|
|
|
|
const { emit = to.emit.bind(to) } = opts
|
|
|
|
const listeners = names.map(
|
|
(name) =>
|
|
function onevent(...args) {
|
|
emit(name, ...args)
|
|
}
|
|
)
|
|
|
|
to.on('newListener', (name) => {
|
|
const i = names.indexOf(name)
|
|
|
|
if (i !== -1 && to.listenerCount(name) === 0) {
|
|
from.on(name, listeners[i])
|
|
}
|
|
}).on('removeListener', (name) => {
|
|
const i = names.indexOf(name)
|
|
|
|
if (i !== -1 && to.listenerCount(name) === 0) {
|
|
from.off(name, listeners[i])
|
|
}
|
|
})
|
|
}
|
|
|
|
exports.listenerCount = function listenerCount(emitter, name) {
|
|
return emitter.listenerCount(name)
|
|
}
|
|
|
|
exports.getMaxListeners = function getMaxListeners(emitter) {
|
|
if (typeof emitter.getMaxListeners === 'function') {
|
|
return emitter.getMaxListeners()
|
|
}
|
|
|
|
return exports.defaultMaxListeners
|
|
}
|
|
|
|
exports.setMaxListeners = function setMaxListeners(n, ...emitters) {
|
|
if (emitters.length === 0) exports.defaultMaxListeners = n
|
|
else {
|
|
for (const emitter of emitters) {
|
|
if (typeof emitter.setMaxListeners === 'function') {
|
|
emitter.setMaxListeners(n)
|
|
}
|
|
}
|
|
}
|
|
}
|