Chore: Simplify results table hooks (#688)
This commit is contained in:
parent
316246e72d
commit
f6f623d0d9
|
@ -42,7 +42,7 @@ classifiers = [
|
|||
]
|
||||
dependencies = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-metadata>=2.0.2",
|
||||
"pytest-metadata>=3.0.0",
|
||||
"Jinja2>=3.0.0",
|
||||
]
|
||||
dynamic = [
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
import datetime
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from pytest_metadata.plugin import metadata_key
|
||||
|
||||
from pytest_html import __version__
|
||||
from pytest_html import extras
|
||||
from pytest_html.table import Header
|
||||
from pytest_html.table import Row
|
||||
from pytest_html.util import cleanup_unserializable
|
||||
|
||||
|
||||
|
@ -60,8 +60,8 @@ class BaseReport:
|
|||
|
||||
self._write_report(rendered_report)
|
||||
|
||||
def _generate_environment(self):
|
||||
metadata = self._config._metadata
|
||||
def _generate_environment(self, metadata_key):
|
||||
metadata = self._config.stash[metadata_key]
|
||||
for key in metadata.keys():
|
||||
value = metadata[key]
|
||||
if self._is_redactable_environment_variable(key):
|
||||
|
@ -145,16 +145,12 @@ class BaseReport:
|
|||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_sessionstart(self, session):
|
||||
config = session.config
|
||||
if hasattr(config, "_metadata") and config._metadata:
|
||||
self._report.set_data("environment", self._generate_environment())
|
||||
self._report.set_data("environment", self._generate_environment(metadata_key))
|
||||
|
||||
session.config.hook.pytest_html_report_title(report=self._report)
|
||||
|
||||
header_cells = Header()
|
||||
session.config.hook.pytest_html_results_table_header(cells=header_cells)
|
||||
self._report.set_data("resultsTableHeader", header_cells.html)
|
||||
self._report.set_data("headerPops", header_cells.get_pops())
|
||||
headers = self._report.data["resultsTableHeader"]
|
||||
session.config.hook.pytest_html_results_table_header(cells=headers)
|
||||
|
||||
self._report.set_data("runningState", "Started")
|
||||
self._generate_report()
|
||||
|
@ -173,7 +169,8 @@ class BaseReport:
|
|||
@pytest.hookimpl(trylast=True)
|
||||
def pytest_terminal_summary(self, terminalreporter):
|
||||
terminalreporter.write_sep(
|
||||
"-", f"Generated html report: file://{self._report_path.resolve()}"
|
||||
"-",
|
||||
f"Generated html report: file://{self._report_path.resolve().as_posix()}",
|
||||
)
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
|
@ -189,34 +186,60 @@ class BaseReport:
|
|||
)
|
||||
|
||||
data = {
|
||||
"duration": report.duration,
|
||||
"result": _process_outcome(report),
|
||||
"duration": _format_duration(report.duration),
|
||||
}
|
||||
|
||||
total_duration = self._report.data["totalDuration"]
|
||||
total_duration["total"] += report.duration
|
||||
total_duration["formatted"] = _format_duration(total_duration["total"])
|
||||
|
||||
test_id = report.nodeid
|
||||
if report.when != "call":
|
||||
test_id += f"::{report.when}"
|
||||
data["testId"] = test_id
|
||||
|
||||
row_cells = Row()
|
||||
self._config.hook.pytest_html_results_table_row(report=report, cells=row_cells)
|
||||
if row_cells.html is None:
|
||||
data["extras"] = self._process_extras(report, test_id)
|
||||
links = [
|
||||
extra
|
||||
for extra in data["extras"]
|
||||
if extra["format_type"] in ["json", "text", "url"]
|
||||
]
|
||||
cells = [
|
||||
f'<td class="col-result">{data["result"]}</td>',
|
||||
f'<td class="col-name">{data["testId"]}</td>',
|
||||
f'<td class="col-duration">{data["duration"]}</td>',
|
||||
f'<td class="col-links">{_process_links(links)}</td>',
|
||||
]
|
||||
|
||||
self._config.hook.pytest_html_results_table_row(report=report, cells=cells)
|
||||
if not cells:
|
||||
return
|
||||
data["resultsTableRow"] = row_cells.html
|
||||
for sortable, value in row_cells.sortables.items():
|
||||
data[sortable] = value
|
||||
|
||||
data["resultsTableRow"] = cells
|
||||
|
||||
processed_logs = _process_logs(report)
|
||||
self._config.hook.pytest_html_results_table_html(
|
||||
report=report, data=processed_logs
|
||||
)
|
||||
|
||||
data["result"] = _process_outcome(report)
|
||||
data["extras"] = self._process_extras(report, test_id)
|
||||
|
||||
if self._report.add_test(data, report, processed_logs):
|
||||
self._generate_report()
|
||||
|
||||
|
||||
def _format_duration(duration):
|
||||
if duration < 1:
|
||||
return "{} ms".format(round(duration * 1000))
|
||||
|
||||
hours = math.floor(duration / 3600)
|
||||
remaining_seconds = duration % 3600
|
||||
minutes = math.floor(remaining_seconds / 60)
|
||||
remaining_seconds = remaining_seconds % 60
|
||||
seconds = round(remaining_seconds)
|
||||
|
||||
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||
|
||||
|
||||
def _is_error(report):
|
||||
return report.when in ["setup", "teardown"] and report.outcome == "failed"
|
||||
|
||||
|
@ -249,3 +272,8 @@ def _process_outcome(report):
|
|||
return "XFailed"
|
||||
|
||||
return report.outcome.capitalize()
|
||||
|
||||
|
||||
def _process_links(links):
|
||||
a_tag = '<a target="_blank" href="{content}" class="col-links__extra {format_type}">{name}</a>'
|
||||
return "".join([a_tag.format_map(link) for link in links])
|
||||
|
|
|
@ -10,14 +10,26 @@ from pytest_html.util import _handle_ansi
|
|||
class ReportData:
|
||||
def __init__(self, config):
|
||||
self._config = config
|
||||
|
||||
default_headers = [
|
||||
'<th class="sortable" data-column-type="result">Result</th>',
|
||||
'<th class="sortable" data-column-type="testId">Test</th>',
|
||||
'<th class="sortable" data-column-type="duration">Duration</th>',
|
||||
"<th>Links</th>",
|
||||
]
|
||||
|
||||
self._data = {
|
||||
"title": "",
|
||||
"collectedItems": 0,
|
||||
"totalDuration": {
|
||||
"total": 0,
|
||||
"formatted": "",
|
||||
},
|
||||
"runningState": "not_started",
|
||||
"environment": {},
|
||||
"tests": defaultdict(list),
|
||||
"resultsTableHeader": {},
|
||||
"additionalSummary": defaultdict(list),
|
||||
"resultsTableHeader": default_headers,
|
||||
}
|
||||
|
||||
collapsed = config.getini("render_collapsed")
|
||||
|
|
|
@ -26,16 +26,9 @@
|
|||
<td></td>
|
||||
</tr>
|
||||
</template>
|
||||
<template id="template_a">
|
||||
<a target="_blank"></a>
|
||||
</template>
|
||||
<template id="template_results-table__tbody">
|
||||
<tbody class="results-table-row">
|
||||
<tr class="collapsible">
|
||||
<td class="col-result"></td>
|
||||
<td class="col-name"></td>
|
||||
<td class="col-duration"></td>
|
||||
<td class="col-links"></td>
|
||||
</tr>
|
||||
<tr class="extras-row">
|
||||
<td class="extra" colspan="4">
|
||||
|
@ -62,10 +55,6 @@
|
|||
<template id="template_results-table__head">
|
||||
<thead id="results-table-head">
|
||||
<tr>
|
||||
<th class="sortable" data-column-type="result">Result</th>
|
||||
<th class="sortable" data-column-type="testId">Test</th>
|
||||
<th class="sortable" data-column-type="duration">Duration</th>
|
||||
<th>Links</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</template>
|
||||
|
|
|
@ -207,7 +207,7 @@ div.media {
|
|||
.sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sortable.asc:after {
|
||||
.sortable.desc:after {
|
||||
content: " ";
|
||||
position: relative;
|
||||
left: 5px;
|
||||
|
@ -217,7 +217,7 @@ div.media {
|
|||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
.sortable.desc:after {
|
||||
.sortable.asc:after {
|
||||
content: " ";
|
||||
position: relative;
|
||||
left: 5px;
|
||||
|
|
|
@ -50,6 +50,9 @@ class DataManager {
|
|||
get isFinished() {
|
||||
return this.data.runningState === 'Finished'
|
||||
}
|
||||
get formattedDuration() {
|
||||
return this.data.totalDuration.formatted
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
const storageModule = require('./storage.js')
|
||||
const { formatDuration, transformTableObj } = require('./utils.js')
|
||||
const mediaViewer = require('./mediaviewer.js')
|
||||
const templateEnvRow = document.querySelector('#template_environment_row')
|
||||
const templateCollGroup = document.querySelector('#template_table-colgroup')
|
||||
const templateResult = document.querySelector('#template_results-table__tbody')
|
||||
const aTag = document.querySelector('#template_a')
|
||||
const listHeader = document.querySelector('#template_results-table__head')
|
||||
const listHeaderEmpty = document.querySelector('#template_results-table__head--empty')
|
||||
|
||||
|
@ -28,12 +26,6 @@ const findAll = (selector, elem) => {
|
|||
return [...elem.querySelectorAll(selector)]
|
||||
}
|
||||
|
||||
const insertAdditionalHTML = (html, element, selector, position = 'beforebegin') => {
|
||||
Object.keys(html).map((key) => {
|
||||
element.querySelectorAll(selector).item(key).insertAdjacentHTML(position, html[key])
|
||||
})
|
||||
}
|
||||
|
||||
const dom = {
|
||||
getStaticRow: (key, value) => {
|
||||
const envRow = templateEnvRow.content.cloneNode(true)
|
||||
|
@ -53,29 +45,14 @@ const dom = {
|
|||
const sortAttr = storageModule.getSort()
|
||||
const sortAsc = JSON.parse(storageModule.getSortDirection())
|
||||
|
||||
const regex = /data-column-type="(\w+)/
|
||||
const cols = Object.values(resultsTableHeader).reduce((result, value) => {
|
||||
if (value.includes('sortable')) {
|
||||
const matches = regex.exec(value)
|
||||
if (matches) {
|
||||
result.push(matches[1])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}, [])
|
||||
const sortables = ['result', 'testId', 'duration', ...cols]
|
||||
|
||||
// Add custom html from the pytest_html_results_table_header hook
|
||||
const headers = transformTableObj(resultsTableHeader)
|
||||
insertAdditionalHTML(headers.inserts, header, 'th')
|
||||
insertAdditionalHTML(headers.appends, header, 'tr', 'beforeend')
|
||||
|
||||
sortables.forEach((sortCol) => {
|
||||
if (sortCol === sortAttr) {
|
||||
header.querySelector(`[data-column-type="${sortCol}"]`).classList.add(sortAsc ? 'desc' : 'asc')
|
||||
}
|
||||
resultsTableHeader.forEach((html) => {
|
||||
const t = document.createElement('template')
|
||||
t.innerHTML = html
|
||||
header.querySelector('#results-table-head > tr').appendChild(t.content)
|
||||
})
|
||||
|
||||
header.querySelector(`.sortable[data-column-type="${sortAttr}"]`).classList.add(sortAsc ? 'desc' : 'asc')
|
||||
|
||||
return header
|
||||
},
|
||||
getListHeaderEmpty: () => listHeaderEmpty.content.cloneNode(true),
|
||||
|
@ -86,12 +63,13 @@ const dom = {
|
|||
resultBody.querySelector('tbody').classList.add(resultLower)
|
||||
resultBody.querySelector('tbody').id = testId
|
||||
resultBody.querySelector('.collapsible').dataset.id = id
|
||||
resultBody.querySelector('.col-result').innerText = result
|
||||
resultBody.querySelector('.col-result').classList.add(`${collapsed ? 'expander' : 'collapser'}`)
|
||||
resultBody.querySelector('.col-name').innerText = testId
|
||||
|
||||
const formattedDuration = duration < 1 ? formatDuration(duration).ms : formatDuration(duration).formatted
|
||||
resultBody.querySelector('.col-duration').innerText = formattedDuration
|
||||
resultsTableRow.forEach((html) => {
|
||||
const t = document.createElement('template')
|
||||
t.innerHTML = html
|
||||
resultBody.querySelector('.collapsible').appendChild(t.content)
|
||||
})
|
||||
resultBody.querySelector('.collapsible > td')?.classList.add(`${collapsed ? 'expander' : 'collapser'}`)
|
||||
|
||||
if (log) {
|
||||
// Wrap lines starting with "E" with span.error to color those lines red
|
||||
|
@ -107,16 +85,6 @@ const dom = {
|
|||
|
||||
const media = []
|
||||
extras?.forEach(({ name, format_type, content }) => {
|
||||
if (['json', 'text', 'url'].includes(format_type)) {
|
||||
const extraLink = aTag.content.cloneNode(true)
|
||||
const extraLinkItem = extraLink.querySelector('a')
|
||||
|
||||
extraLinkItem.href = content
|
||||
extraLinkItem.className = `col-links__extra ${format_type}`
|
||||
extraLinkItem.innerText = name
|
||||
resultBody.querySelector('.col-links').appendChild(extraLinkItem)
|
||||
}
|
||||
|
||||
if (['image', 'video'].includes(format_type)) {
|
||||
media.push({ path: content, name, format_type })
|
||||
}
|
||||
|
@ -127,11 +95,6 @@ const dom = {
|
|||
})
|
||||
mediaViewer.setUp(resultBody, media)
|
||||
|
||||
// Add custom html from the pytest_html_results_table_row hook
|
||||
const rows = transformTableObj(resultsTableRow)
|
||||
resultsTableRow && insertAdditionalHTML(rows.inserts, resultBody, 'td')
|
||||
resultsTableRow && insertAdditionalHTML(rows.appends, resultBody, 'tr', 'beforeend')
|
||||
|
||||
// Add custom html from the pytest_html_results_table_html hook
|
||||
tableHtml?.forEach((item) => {
|
||||
resultBody.querySelector('td[class="extra"]').insertAdjacentHTML('beforeend', item)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
const { formatDuration } = require('./utils.js')
|
||||
const { dom, findAll } = require('./dom.js')
|
||||
const { manager } = require('./datamanager.js')
|
||||
const { doSort } = require('./sort.js')
|
||||
|
@ -52,18 +51,6 @@ const renderContent = (tests) => {
|
|||
item.colSpan = document.querySelectorAll('th').length
|
||||
})
|
||||
|
||||
const { headerPops } = manager.renderData
|
||||
if (headerPops > 0) {
|
||||
// remove 'headerPops' number of header columns
|
||||
findAll('#results-table-head th').splice(-headerPops).forEach((column) => column.remove())
|
||||
|
||||
// remove 'headerPops' number of row columns
|
||||
const resultRows = findAll('.results-table-row')
|
||||
resultRows.forEach((elem) => {
|
||||
findAll('td:not(.extra)', elem).splice(-headerPops).forEach((column) => column.remove())
|
||||
})
|
||||
}
|
||||
|
||||
findAll('.sortable').forEach((elem) => {
|
||||
elem.addEventListener('click', (evt) => {
|
||||
const { target: element } = evt
|
||||
|
@ -80,7 +67,7 @@ const renderContent = (tests) => {
|
|||
})
|
||||
}
|
||||
|
||||
const renderDerived = (tests, collectedItems, isFinished) => {
|
||||
const renderDerived = (tests, collectedItems, isFinished, formattedDuration) => {
|
||||
const currentFilter = getVisible()
|
||||
possibleResults.forEach(({ result, label }) => {
|
||||
const count = tests.filter((test) => test.result.toLowerCase() === result).length
|
||||
|
@ -100,12 +87,8 @@ const renderDerived = (tests, collectedItems, isFinished) => {
|
|||
['Passed', 'Failed', 'XPassed', 'XFailed'].includes(result)).length
|
||||
|
||||
if (isFinished) {
|
||||
const accTime = tests.reduce((prev, { duration }) => prev + duration, 0)
|
||||
const formattedAccTime = formatDuration(accTime)
|
||||
const testWord = numberOfTests > 1 ? 'tests' : 'test'
|
||||
const durationText = formattedAccTime.hasOwnProperty('ms') ? formattedAccTime.ms : formattedAccTime.formatted
|
||||
|
||||
document.querySelector('.run-count').innerText = `${numberOfTests} ${testWord} took ${durationText}.`
|
||||
document.querySelector('.run-count').innerText = `${numberOfTests} ${testWord} took ${formattedDuration}.`
|
||||
document.querySelector('.summary__reload__button').classList.add('hidden')
|
||||
} else {
|
||||
document.querySelector('.run-count').innerText = `${numberOfTests} / ${collectedItems} tests done`
|
||||
|
@ -135,11 +118,11 @@ const bindEvents = () => {
|
|||
}
|
||||
|
||||
const redraw = () => {
|
||||
const { testSubset, allTests, collectedItems, isFinished } = manager
|
||||
const { testSubset, allTests, collectedItems, isFinished, formattedDuration } = manager
|
||||
|
||||
renderStatic()
|
||||
renderContent(testSubset)
|
||||
renderDerived(allTests, collectedItems, isFinished)
|
||||
renderDerived(allTests, collectedItems, isFinished, formattedDuration )
|
||||
}
|
||||
|
||||
exports.redraw = redraw
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
const formattedNumber = (number) =>
|
||||
number.toLocaleString('en-US', {
|
||||
minimumIntegerDigits: 2,
|
||||
useGrouping: false,
|
||||
})
|
||||
|
||||
const formatDuration = ( totalSeconds ) => {
|
||||
if (totalSeconds < 1) {
|
||||
return { ms: `${Math.round(totalSeconds * 1000)} ms` }
|
||||
}
|
||||
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
let remainingSeconds = totalSeconds % 3600
|
||||
const minutes = Math.floor(remainingSeconds / 60)
|
||||
remainingSeconds = remainingSeconds % 60
|
||||
const seconds = Math.round(remainingSeconds)
|
||||
|
||||
return {
|
||||
seconds: `${Math.round(totalSeconds)} seconds`,
|
||||
formatted: `${formattedNumber(hours)}:${formattedNumber(minutes)}:${formattedNumber(seconds)}`,
|
||||
}
|
||||
}
|
||||
|
||||
const transformTableObj = (obj) => {
|
||||
const appends = {}
|
||||
const inserts = {}
|
||||
for (const key in obj) {
|
||||
if (Object.hasOwn(obj, key)) {
|
||||
key.startsWith('Z') ? appends[key] = obj[key] : inserts[key] = obj[key]
|
||||
}
|
||||
}
|
||||
return {
|
||||
appends,
|
||||
inserts,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatDuration,
|
||||
transformTableObj,
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
import re
|
||||
import warnings
|
||||
|
||||
|
||||
class Table:
|
||||
def __init__(self):
|
||||
self._html = {}
|
||||
|
||||
@property
|
||||
def html(self):
|
||||
return self._html
|
||||
|
||||
@html.setter
|
||||
def html(self, value):
|
||||
self._html = value
|
||||
|
||||
|
||||
class Cell(Table):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._append_counter = 0
|
||||
self._pop_counter = 0
|
||||
self._sortables = dict()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
warnings.warn(
|
||||
"list-type assignment is deprecated and support "
|
||||
"will be removed in a future release. "
|
||||
"Please use 'insert()' instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
self.insert(key, value)
|
||||
|
||||
@property
|
||||
def sortables(self):
|
||||
return self._sortables
|
||||
|
||||
def append(self, item):
|
||||
# We need a way of separating inserts from appends in JS,
|
||||
# hence the "Z" prefix
|
||||
self.insert(f"Z{self._append_counter}", item)
|
||||
self._append_counter += 1
|
||||
|
||||
def insert(self, index, html):
|
||||
# backwards-compat
|
||||
if not isinstance(html, str):
|
||||
if html.__module__.startswith("py."):
|
||||
warnings.warn(
|
||||
"The 'py' module is deprecated and support "
|
||||
"will be removed in a future release.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
html = str(html)
|
||||
html = html.replace("col=", "data-column-type=")
|
||||
self._extract_sortable(html)
|
||||
self._html[index] = html
|
||||
|
||||
def pop(self, *args):
|
||||
self._pop_counter += 1
|
||||
|
||||
def get_pops(self):
|
||||
return self._pop_counter
|
||||
|
||||
def _extract_sortable(self, html):
|
||||
match = re.search(r'<td class="col-(\w+)">(.*?)</', html)
|
||||
if match:
|
||||
sortable = match.group(1)
|
||||
value = match.group(2)
|
||||
self._sortables[sortable] = value
|
||||
|
||||
|
||||
class Header(Cell):
|
||||
pass
|
||||
|
||||
|
||||
class Row(Cell):
|
||||
def __delitem__(self, key):
|
||||
# This means the item should be removed
|
||||
self._html = None
|
||||
|
||||
def pop(self, *args):
|
||||
# Calling pop on header is sufficient
|
||||
pass
|
|
@ -107,7 +107,6 @@ class TestHTML:
|
|||
def test_can_format_duration_column(
|
||||
self, testdir, duration_formatter, expected_report_content
|
||||
):
|
||||
|
||||
testdir.makeconftest(
|
||||
f"""
|
||||
import pytest
|
||||
|
|
|
@ -650,7 +650,7 @@ class TestHTML:
|
|||
pytester.makepyfile("def test_pass(): pass")
|
||||
page = run(pytester)
|
||||
|
||||
description_index = 3
|
||||
description_index = 4
|
||||
time_index = 2
|
||||
assert_that(get_text(page, header_selector.format(time_index))).is_equal_to(
|
||||
"Time"
|
||||
|
|
|
@ -2,7 +2,6 @@ const { expect } = require('chai')
|
|||
const sinon = require('sinon')
|
||||
const { doInitFilter, doFilter } = require('../src/pytest_html/scripts/filter.js')
|
||||
const { doInitSort, doSort } = require('../src/pytest_html/scripts/sort.js')
|
||||
const { formatDuration, transformTableObj } = require('../src/pytest_html/scripts/utils.js')
|
||||
const dataModule = require('../src/pytest_html/scripts/datamanager.js')
|
||||
const storageModule = require('../src/pytest_html/scripts/storage.js')
|
||||
|
||||
|
@ -154,37 +153,6 @@ describe('Sort tests', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('utils tests', () => {
|
||||
describe('formatDuration', () => {
|
||||
it('handles small durations', () => {
|
||||
expect(formatDuration(0.123).ms).to.eql('123 ms')
|
||||
expect(formatDuration(0).ms).to.eql('0 ms')
|
||||
expect(formatDuration(0.999).ms).to.eql('999 ms')
|
||||
})
|
||||
it('handles larger durations', () => {
|
||||
expect(formatDuration(1.234).formatted).to.eql('00:00:01')
|
||||
expect(formatDuration(12345.678).formatted).to.eql('03:25:46')
|
||||
})
|
||||
})
|
||||
describe('transformTableObj', () => {
|
||||
it('handles empty object', () => {
|
||||
expect(transformTableObj({})).to.eql({ appends: {}, inserts: {} })
|
||||
})
|
||||
it('handles no appends', () => {
|
||||
const expected = { 1: 'hello', 2: 'goodbye' }
|
||||
expect(transformTableObj(expected)).to.eql({ appends: {}, inserts: expected })
|
||||
})
|
||||
it('handles no inserts', () => {
|
||||
const expected = { 'Z1': 'hello', 'Z2': 'goodbye' }
|
||||
expect(transformTableObj(expected)).to.eql({ appends: expected, inserts: {} })
|
||||
})
|
||||
it('handles both', () => {
|
||||
const expected = { appends: { 'Z1': 'hello', 'Z2': 'goodbye' }, inserts: { 1: 'mee', 2: 'moo' } }
|
||||
expect(transformTableObj({ ...expected.appends, ...expected.inserts })).to.eql(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Storage tests', () => {
|
||||
describe('getCollapsedCategory', () => {
|
||||
let originalWindow
|
||||
|
|
Loading…
Reference in New Issue