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.
110 lines
2.4 KiB
JavaScript
110 lines
2.4 KiB
JavaScript
class RetryOperation {
|
|
#attempts = 1
|
|
#cachedTimeouts = null
|
|
#errors = []
|
|
#fn = null
|
|
#maxRetryTime
|
|
#operationStart = null
|
|
#originalTimeouts
|
|
#timeouts
|
|
#timer = null
|
|
#unref
|
|
|
|
constructor (timeouts, options = {}) {
|
|
this.#originalTimeouts = [...timeouts]
|
|
this.#timeouts = [...timeouts]
|
|
this.#unref = options.unref
|
|
this.#maxRetryTime = options.maxRetryTime || Infinity
|
|
if (options.forever) {
|
|
this.#cachedTimeouts = [...this.#timeouts]
|
|
}
|
|
}
|
|
|
|
get timeouts () {
|
|
return [...this.#timeouts]
|
|
}
|
|
|
|
get errors () {
|
|
return [...this.#errors]
|
|
}
|
|
|
|
get attempts () {
|
|
return this.#attempts
|
|
}
|
|
|
|
get mainError () {
|
|
let mainError = null
|
|
if (this.#errors.length) {
|
|
let mainErrorCount = 0
|
|
const counts = {}
|
|
for (let i = 0; i < this.#errors.length; i++) {
|
|
const error = this.#errors[i]
|
|
const { message } = error
|
|
if (!counts[message]) {
|
|
counts[message] = 0
|
|
}
|
|
counts[message]++
|
|
|
|
if (counts[message] >= mainErrorCount) {
|
|
mainError = error
|
|
mainErrorCount = counts[message]
|
|
}
|
|
}
|
|
}
|
|
return mainError
|
|
}
|
|
|
|
reset () {
|
|
this.#attempts = 1
|
|
this.#timeouts = [...this.#originalTimeouts]
|
|
}
|
|
|
|
stop () {
|
|
if (this.#timer) {
|
|
clearTimeout(this.#timer)
|
|
}
|
|
|
|
this.#timeouts = []
|
|
this.#cachedTimeouts = null
|
|
}
|
|
|
|
retry (err) {
|
|
this.#errors.push(err)
|
|
if (new Date().getTime() - this.#operationStart >= this.#maxRetryTime) {
|
|
// XXX This puts the timeout error first, meaning it will never show as mainError, there may be no way to ever see this
|
|
this.#errors.unshift(new Error('RetryOperation timeout occurred'))
|
|
return false
|
|
}
|
|
|
|
let timeout = this.#timeouts.shift()
|
|
if (timeout === undefined) {
|
|
// We're out of timeouts, clear the last error and repeat the final timeout
|
|
if (this.#cachedTimeouts) {
|
|
this.#errors.pop()
|
|
timeout = this.#cachedTimeouts.at(-1)
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// TODO what if there already is a timer?
|
|
this.#timer = setTimeout(() => {
|
|
this.#attempts++
|
|
this.#fn(this.#attempts)
|
|
}, timeout)
|
|
|
|
if (this.#unref) {
|
|
this.#timer.unref()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
attempt (fn) {
|
|
this.#fn = fn
|
|
this.#operationStart = new Date().getTime()
|
|
this.#fn(this.#attempts)
|
|
}
|
|
}
|
|
module.exports = { RetryOperation }
|