Skip to content

Commit

Permalink
Made whole countdown card clickable (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcokreeft87 authored Jan 28, 2023
1 parent f644e7d commit ba62f2e
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 27 deletions.
17 changes: 9 additions & 8 deletions formulaone-card.js

Large diffs are not rendered by default.

Binary file modified formulaone-card.js.gz
Binary file not shown.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "formulaone-card",
"version": "0.7.0",
"version": "0.7.1",
"description": "Frontend card for Home Assistant to display Formula One data",
"main": "index.js",
"scripts": {
Expand Down
22 changes: 17 additions & 5 deletions src/cards/countdown.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { html, HTMLTemplateResult } from "lit-html";
import { until } from 'lit-html/directives/until.js';
import { getApiErrorMessage, getApiLoadingMessage, getCountryFlagByName, getEndOfSeasonMessage, renderHeader, renderRaceInfo } from "../utils";
import { clickHandler, getApiErrorMessage, getApiLoadingMessage, getCountryFlagByName, getEndOfSeasonMessage, renderHeader, renderRaceInfo } from "../utils";
import { BaseCard } from "./base-card";
import { asyncReplace } from 'lit/directives/async-replace.js';
import { Race } from "../api/models";
import { HomeAssistant } from "custom-card-helpers";
import { ActionHandlerEvent, hasAction, HomeAssistant } from "custom-card-helpers";
import FormulaOneCard from "..";
import { actionHandler } from "../directives/action-handler-directive";

export default class Countdown extends BaseCard {
hass: HomeAssistant;
Expand Down Expand Up @@ -40,7 +41,7 @@ export default class Countdown extends BaseCard {

renderHeader(race: Race): HTMLTemplateResult {
return this.config.show_raceinfo ?
html`<table><tr><td colspan="5">${renderHeader(this, race)}</td></tr>
html`<table><tr><td colspan="5">${renderHeader(this, race, true)}</td></tr>
${renderRaceInfo(this.hass, this.config, race, this)}</table>`
: null;
}
Expand Down Expand Up @@ -68,6 +69,12 @@ export default class Countdown extends BaseCard {

render() : HTMLTemplateResult {

const _handleAction = (ev: ActionHandlerEvent): void => {
if (this.hass && this.config.actions && ev.detail.action) {
clickHandler(this.parent, this.config, this.hass, ev);
}
}

return html`${until(
this.client.GetSchedule(new Date().getFullYear()).then(response => {
if(!response) {
Expand All @@ -85,9 +92,14 @@ export default class Countdown extends BaseCard {
}
const raceDateTime = new Date(nextRace.date + 'T' + nextRace.time);
const timer = this.countDownTillDate(raceDateTime);
const timer = this.countDownTillDate(raceDateTime);
const hasConfigAction = this.config.actions !== undefined;
return html`<table>
return html`<table @action=${_handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this.config.actions?.hold_action),
hasDoubleClick: hasAction(this.config.actions?.double_tap_action),
})} class="${(hasConfigAction ? 'clickable' : null)}">
<tr>
<td>
<h2><img height="25" src="${getCountryFlagByName(nextRace.Circuit.Location.country)}">&nbsp;&nbsp; ${nextRace.round} : ${nextRace.raceName}</h2>
Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ export const clickHandler = (node: LitElement, config: FormulaOneCardConfig, has
handleAction(node, hass, config.actions, ev.detail.action);
}

export const renderHeader = (card: BaseCard, race: Race): HTMLTemplateResult => {
export const renderHeader = (card: BaseCard, race: Race, preventClick = false): HTMLTemplateResult => {

const countryDashed = race.Circuit.Location.country.replace(" ","-")
const circuitName = getCircuitName(countryDashed);

const _handleAction = (ev: ActionHandlerEvent): void => {
if (card.hass && card.config.actions && ev.detail.action) {
if (card.hass && card.config.actions && ev.detail.action && !preventClick) {
clickHandler(card.parent, card.config, card.hass, ev);
}
}
Expand Down
57 changes: 49 additions & 8 deletions tests/cards/countdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Mrdata, Race, Root } from "../../src/api/models";
import { HTMLTemplateResult } from "lit";
import { HomeAssistant, NumberFormat, TimeFormat } from "custom-card-helpers";
import FormulaOneCard from "../../src";
import * as customCardHelper from "custom-card-helpers";

describe('Testing countdown file', () => {
const parent = createMock<FormulaOneCard>({
Expand Down Expand Up @@ -88,7 +89,7 @@ describe('Testing countdown file', () => {
const { htmlResult, date } = await getHtmlResultAndDate(card);
jest.useRealTimers();

expect(htmlResult).toMatch('<table> <tr> <td> <h2><img height="25" src="https://flagcdn.com/w40/bh.png">&nbsp;&nbsp; 1 : Bahrain Grand Prix</h2> </td> </tr> <tr> <td class="text-center"> <h1></h1> </td> </tr> </table>');
expect(htmlResult).toMatch('<table @action=_handleAction .actionHandler= class=""> <tr> <td> <h2><img height="25" src="https://flagcdn.com/w40/bh.png">&nbsp;&nbsp; 1 : Bahrain Grand Prix</h2> </td> </tr> <tr> <td class="text-center"> <h1></h1> </td> </tr> </table>');
expect(date.value).toMatch('19d 16h 0m 0s');
}),
test('Calling render with date equal to race start should render we are racing', async () => {
Expand All @@ -99,7 +100,7 @@ describe('Testing countdown file', () => {
const { htmlResult, date } = await getHtmlResultAndDate(card);
jest.useRealTimers();

expect(htmlResult).toMatch('<table> <tr> <td> <h2><img height="25" src="https://flagcdn.com/w40/bh.png">&nbsp;&nbsp; 1 : Bahrain Grand Prix</h2> </td> </tr> <tr> <td class="text-center"> <h1></h1> </td> </tr> </table>');
expect(htmlResult).toMatch('<table @action=_handleAction .actionHandler= class=""> <tr> <td> <h2><img height="25" src="https://flagcdn.com/w40/bh.png">&nbsp;&nbsp; 1 : Bahrain Grand Prix</h2> </td> </tr> <tr> <td class="text-center"> <h1></h1> </td> </tr> </table>');
expect(date.value).toMatch('We are racing!');
}),
test('Calling render with date an hour past race start render we are racing', async () => {
Expand All @@ -115,7 +116,7 @@ describe('Testing countdown file', () => {
const { htmlResult, date } = await getHtmlResultAndDate(card);
jest.useRealTimers();

expect(htmlResult).toMatch('<table> <tr> <td> <h2><img height="25" src="https://flagcdn.com/w40/bh.png">&nbsp;&nbsp; 1 : Bahrain Grand Prix</h2> </td> </tr> <tr> <td class="text-center"> <h1></h1> </td> </tr> </table>');
expect(htmlResult).toMatch('<table @action=_handleAction .actionHandler= class=""> <tr> <td> <h2><img height="25" src="https://flagcdn.com/w40/bh.png">&nbsp;&nbsp; 1 : Bahrain Grand Prix</h2> </td> </tr> <tr> <td class="text-center"> <h1></h1> </td> </tr> </table>');
expect(date.value).toMatch('We are racing!');
}),
test('Calling render with date an day past race start render we are racing', async () => {
Expand All @@ -126,7 +127,7 @@ describe('Testing countdown file', () => {
const { htmlResult, date } = await getHtmlResultAndDate(card);
jest.useRealTimers();

expect(htmlResult).toMatch('<table> <tr> <td> <h2><img height="25" src="https://flagcdn.com/w40/sa.png">&nbsp;&nbsp; 2 : Saudi Arabian Grand Prix</h2> </td> </tr> <tr> <td class="text-center"> <h1></h1> </td> </tr> </table>');
expect(htmlResult).toMatch('<table @action=_handleAction .actionHandler= class=""> <tr> <td> <h2><img height="25" src="https://flagcdn.com/w40/sa.png">&nbsp;&nbsp; 2 : Saudi Arabian Grand Prix</h2> </td> </tr> <tr> <td class="text-center"> <h1></h1> </td> </tr> </table>');
expect(date.value).toMatch('6d 18h 0m 0s');
}),
test('Calling render with date end of season', async () => {
Expand All @@ -138,7 +139,7 @@ describe('Testing countdown file', () => {
const htmlResult = await getRenderStringAsync(result);
jest.useRealTimers();

expect(htmlResult).toMatch('<table><tr><td class="text-center"><ha-icon icon="mdi:flag-checkered"></ha-icon><strong>Season is over. See you next year!</strong><ha-icon icon="mdi:flag-checkered"></ha-icon></td></tr></table><table>');
expect(htmlResult).toMatch('<table><tr><td class="text-center"><ha-icon icon="mdi:flag-checkered"></ha-icon><strong>Season is over. See you next year!</strong><ha-icon icon="mdi:flag-checkered"></ha-icon></td></tr></table><table><tr><td class="text-center"><ha-icon icon="mdi:car-speed-limiter"></ha-icon> Loading... <ha-icon icon="mdi:car-speed-limiter"></ha-icon></td></tr></table>');
}),
test('Calling render with api not returning data', async () => {

Expand Down Expand Up @@ -177,7 +178,7 @@ describe('Testing countdown file', () => {
jest.useRealTimers();

expect(htmlResult).toBe('');
})
}),
test.each`
show_raceinfo | expected
${undefined}, ${6}
Expand All @@ -188,18 +189,58 @@ describe('Testing countdown file', () => {
card.config = config;

expect(card.cardSize()).toBe(expected);
}),
test('Calling render with actions', async () => {

const spy = jest.spyOn(customCardHelper, 'handleAction');

card.config.actions = {
tap_action: {
action: 'navigate',
navigation_path: '/lovelace/0',
},
hold_action: {
action: 'navigate',
navigation_path: '/lovelace/1',
},
double_tap_action: {
action: 'navigate',
navigation_path: '/lovelace/2',
}
}

jest.useFakeTimers();
jest.setSystemTime(new Date(2022, 2, 1)); // Weird bug in jest setting this to the last of the month

const { htmlResult, date, handleAction } = await getHtmlResultAndDate(card);
jest.useRealTimers();

expect(htmlResult).toMatch('<table @action=_handleAction .actionHandler= class="clickable"> <tr> <td> <h2><img height="25" src="https://flagcdn.com/w40/bh.png">&nbsp;&nbsp; 1 : Bahrain Grand Prix</h2> </td> </tr> <tr> <td class="text-center"> <h1></h1> </td> </tr> </table>');
expect(date.value).toMatch('19d 16h 0m 0s');

// eslint-disable-next-line @typescript-eslint/ban-types
handleAction({ detail: { action: 'tap' } });
handleAction({ detail: { action: 'double_tap' } });
handleAction({ detail: { action: 'hold' } });

expect(customCardHelper.handleAction).toBeCalledTimes(3);

spy.mockClear();
});
});

async function getHtmlResultAndDate(card: Countdown) {
const result = card.render();

const promise = (result.values[0] as HTMLTemplateResult).values[0] as Promise<HTMLTemplateResult>;
const promiseResult = await promise;

const iterator = (promiseResult.values[3] as HTMLTemplateResult).values[0] as AsyncIterableIterator<HTMLTemplateResult>;
const iterator = (promiseResult.values[6] as HTMLTemplateResult).values[0] as AsyncIterableIterator<HTMLTemplateResult>;
// eslint-disable-next-line @typescript-eslint/ban-types
const handleAction = promiseResult.values[0] as Function;

const date = await iterator.next();

const htmlResult = await getRenderStringAsync(promiseResult);
return { htmlResult, date };
return { htmlResult, date, handleAction };
}
36 changes: 35 additions & 1 deletion tests/utils/renderHeader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe('Testing util file function renderHeader', () => {
test('Calling renderHeader with actions', () => {

// handleAction
jest.spyOn(customCardHelper, 'handleAction');
const spy = jest.spyOn(customCardHelper, 'handleAction');

card.config.actions = {
tap_action: {
Expand All @@ -79,5 +79,39 @@ describe('Testing util file function renderHeader', () => {
actionHandler({ detail: { action: 'hold' } });

expect(customCardHelper.handleAction).toBeCalledTimes(3);

spy.mockClear();
}),
test('Calling renderHeader with actions', () => {

// handleAction
const spy = jest.spyOn(customCardHelper, 'handleAction');

card.config.actions = {
tap_action: {
action: 'navigate',
navigation_path: '/lovelace/0',
},
hold_action: {
action: 'navigate',
navigation_path: '/lovelace/1',
},
double_tap_action: {
action: 'navigate',
navigation_path: '/lovelace/2',
}
}

const result = renderHeader(card, lastRace, true);

// eslint-disable-next-line @typescript-eslint/ban-types
const actionHandler = (result.values[1] as HTMLTemplateResult).values[2] as Function;
actionHandler({ detail: { action: 'tap' } });
actionHandler({ detail: { action: 'double_tap' } });
actionHandler({ detail: { action: 'hold' } });

expect(customCardHelper.handleAction).toBeCalledTimes(0);

spy.mockClear();
})
});

0 comments on commit ba62f2e

Please sign in to comment.