Skip to content

Commit

Permalink
Assume audio is playing and that it has controls based on attributes (#…
Browse files Browse the repository at this point in the history
…1538)

* Update audio applicability

* Add tests for autoplay case

* Update audio applicability

It now ask about play button when controls attribute is present on
element

* Add changeset

* Add tests for audio elements with autoplay and controls attributes

These tests exemplify that the `is-playing` and `play-button` questions are not needed anymore for the rules to pass or fail when the relevant attributes are present.

* Fix test
  • Loading branch information
rcj-siteimprove authored Dec 19, 2023
1 parent 23789a3 commit f49666e
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/tasty-stingrays-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@siteimprove/alfa-rules": patch
---

**Changed:** Media rules R23 and R29 no longer asks if audio is playing or where the play buttons is when the attributes `autoplay` and `controls` are present respectively.
28 changes: 16 additions & 12 deletions packages/alfa-rules/src/common/applicability/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Style } from "@siteimprove/alfa-style";
import { Question } from "../act/question";

const { isPerceivableForAll } = DOM;
const { isElement, hasName, hasNamespace } = Element;
const { isElement, hasName, hasNamespace, hasAttribute } = Element;
const { and } = Predicate;
const { isRendered } = Style;
const { getElementDescendants } = Query;
Expand All @@ -44,17 +44,21 @@ export function audio(
Question.of("is-audio-streaming", element).map((isStreaming) =>
isStreaming
? None
: Question.of("is-playing", element).map((isPlaying) =>
isPlaying
? Option.of(element)
: Question.of("play-button", element).map((playButton) =>
playButton.some(
and(isElement, isPerceivableForAll(device)),
)
? Option.of(element)
: None,
),
),
: Question.of("is-playing", element)
.answerIf(hasAttribute("autoplay"), true)
.map((isPlaying) =>
isPlaying
? Option.of(element)
: Question.of("play-button", element)
.answerIf(hasAttribute("controls"), Option.of(element))
.map((playButton) =>
playButton.some(
and(isElement, isPerceivableForAll(device)),
)
? Option.of(element)
: None,
),
),
),
),
);
Expand Down
135 changes: 135 additions & 0 deletions packages/alfa-rules/test/sia-r23/rule.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,59 @@ test(`evaluate() passes an audio with perceivable transcript`, async (t) => {
);
});

test(`evaluate() passes an audio with autoplay attribute and with perceivable transcript`, async (t) => {
const target = <audio src="foo.mp3" autoplay />;
const transcript = <div>Hello</div>;

const document = h.document([target, transcript]);

t.deepEqual(
await evaluate(
R23,
{ document },
oracle({
"is-audio-streaming": false,
transcript: Option.of(transcript),
}),
),
[
passed(
R23,
target,
{ 1: Outcomes.HasPerceivableTranscript("<audio>") },
Outcome.Mode.SemiAuto,
),
],
);
});

test(`evaluate() passes a non-playing audio with controls attribute and with perceivable transcript`, async (t) => {
const target = <audio src="foo.mp3" controls />;
const transcript = <div>Hello</div>;

const document = h.document([target, transcript]);

t.deepEqual(
await evaluate(
R23,
{ document },
oracle({
"is-audio-streaming": false,
"is-playing": false,
transcript: Option.of(transcript),
}),
),
[
passed(
R23,
target,
{ 1: Outcomes.HasPerceivableTranscript("<audio>") },
Outcome.Mode.SemiAuto,
),
],
);
});

test(`evaluate() passes an audio with a link to a transcript`, async (t) => {
const target = <audio src="foo.mp3" />;
const transcript = <a href="transcript.html">Read transcript</a>;
Expand Down Expand Up @@ -92,6 +145,59 @@ test(`evaluate() fails an audio with no transcript`, async (t) => {
);
});

test(`evaluate() fails an audio with autoplay attribute and no transcript`, async (t) => {
const target = <audio src="foo.mp3" autoplay />;

const document = h.document([target]);

t.deepEqual(
await evaluate(
R23,
{ document },
oracle({
"is-audio-streaming": false,
transcript: None,
"transcript-link": None,
}),
),
[
failed(
R23,
target,
{ 1: Outcomes.HasNoTranscriptLink("<audio>") },
Outcome.Mode.SemiAuto,
),
],
);
});

test(`evaluate() fails a non-playing audio with controls attribute and no transcript`, async (t) => {
const target = <audio src="foo.mp3" controls />;

const document = h.document([target]);

t.deepEqual(
await evaluate(
R23,
{ document },
oracle({
"is-audio-streaming": false,
"is-playing": false,
transcript: None,
"transcript-link": None,
}),
),
[
failed(
R23,
target,
{ 1: Outcomes.HasNoTranscriptLink("<audio>") },
Outcome.Mode.SemiAuto,
),
],
);
});

test(`evaluates() fails an audio with non-perceivable transcript`, async (t) => {
const target = <audio src="foo.mp3" />;
const transcript = <div style={{ visibility: "hidden" }}>Hello</div>;
Expand Down Expand Up @@ -200,6 +306,35 @@ test(`evaluate() cannot tell if questions are left unanswered`, async (t) => {
);
});

test(`evaluate() cannot tell for audio with autoplay attribute and not answered expectation questions`, async (t) => {
const target = <audio src="foo.mp3" autoplay />;

const document = h.document([target]);

t.deepEqual(
await evaluate(R23, { document }, oracle({ "is-audio-streaming": false })),
[cantTell(R23, target, undefined, Outcome.Mode.SemiAuto)],
);
});

test(`evaluate() cannot tell for non-playing audio with controls attribute and not answered expectation questions`, async (t) => {
const target = <audio src="foo.mp3" controls />;

const document = h.document([target]);

t.deepEqual(
await evaluate(
R23,
{ document },
oracle({
"is-audio-streaming": false,
"is-playing": false,
}),
),
[cantTell(R23, target, undefined, Outcome.Mode.SemiAuto)],
);
});

test(`evaluate() is inapplicable when Applicability questions are unanswered`, async (t) => {
const target = <audio src="foo.mp3" />;

Expand Down
153 changes: 153 additions & 0 deletions packages/alfa-rules/test/sia-r29/rule.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,69 @@ test(`evaluate() passes an audio that is labelled as alternative for text`, asyn
);
});

test(`evaluate() passes an audio with autoplay attribute that is labelled as alternative for text`, async (t) => {
const target = <audio src="foo.mp3" autoplay />;
const text = <p>Some very long text</p>;
const label = <span>Listen to this content as audio</span>;

const document = h.document([text, label, target]);

t.deepEqual(
await evaluate(
R29,
{ document },
oracle({
"is-audio-streaming": false,
"text-alternative": Option.of(text),
label: Option.of(label),
}),
),
[
passed(
R29,
target,
{
1: Outcomes.HasPerceivableAlternative("<audio>"),
2: Outcomes.HasPerceivableLabel("<audio>"),
},
Outcome.Mode.SemiAuto,
),
],
);
});

test(`evaluate() passes an non-playing audio with controls attribute that is labelled as alternative for text`, async (t) => {
const target = <audio src="foo.mp3" controls />;
const text = <p>Some very long text</p>;
const label = <span>Listen to this content as audio</span>;

const document = h.document([text, label, target]);

t.deepEqual(
await evaluate(
R29,
{ document },
oracle({
"is-audio-streaming": false,
"is-playing": false,
"text-alternative": Option.of(text),
label: Option.of(label),
}),
),
[
passed(
R29,
target,
{
1: Outcomes.HasPerceivableAlternative("<audio>"),
2: Outcomes.HasPerceivableLabel("<audio>"),
},
Outcome.Mode.SemiAuto,
),
],
);
});

test(`evaluate() fails an audio that is not alternative for text`, async (t) => {
const target = <audio src="foo.mp3" />;
const label = <span>Listen to this content as audio</span>;
Expand Down Expand Up @@ -73,6 +136,67 @@ test(`evaluate() fails an audio that is not alternative for text`, async (t) =>
);
});

test(`evaluate() fails an audio with autoplay attribute that is not alternative for text`, async (t) => {
const target = <audio src="foo.mp3" autoplay />;
const label = <span>Listen to this content as audio</span>;

const document = h.document([label, target]);

t.deepEqual(
await evaluate(
R29,
{ document },
oracle({
"is-audio-streaming": false,
"text-alternative": None,
label: Option.of(label),
}),
),
[
failed(
R29,
target,
{
1: Outcomes.HasNoAlternative("<audio>"),
2: Outcomes.HasPerceivableLabel("<audio>"),
},
Outcome.Mode.SemiAuto,
),
],
);
});

test(`evaluate() fails a non-playing audio with controls attribute that is not alternative for text`, async (t) => {
const target = <audio src="foo.mp3" controls />;
const label = <span>Listen to this content as audio</span>;

const document = h.document([label, target]);

t.deepEqual(
await evaluate(
R29,
{ document },
oracle({
"is-audio-streaming": false,
"is-playing": false,
"text-alternative": None,
label: Option.of(label),
}),
),
[
failed(
R29,
target,
{
1: Outcomes.HasNoAlternative("<audio>"),
2: Outcomes.HasPerceivableLabel("<audio>"),
},
Outcome.Mode.SemiAuto,
),
],
);
});

test(`evaluate() fails an audio that is not labelled as alternative for text`, async (t) => {
const target = <audio src="foo.mp3" />;
const text = <p>Some very long text</p>;
Expand Down Expand Up @@ -187,6 +311,35 @@ test(`evaluate() cannot tell if questions are left unanswered`, async (t) => {
);
});

test(`evaluate() cannot tell for audio with autoplay attribute and not answered expectation questions`, async (t) => {
const target = <audio src="foo.mp3" autoplay />;

const document = h.document([target]);

t.deepEqual(
await evaluate(R29, { document }, oracle({ "is-audio-streaming": false })),
[cantTell(R29, target, undefined, Outcome.Mode.SemiAuto)],
);
});

test(`evaluate() cannot tell for non-playing audio with controls attribute and not answered expectation questions`, async (t) => {
const target = <audio src="foo.mp3" controls />;

const document = h.document([target]);

t.deepEqual(
await evaluate(
R29,
{ document },
oracle({
"is-audio-streaming": false,
"is-playing": false,
}),
),
[cantTell(R29, target, undefined, Outcome.Mode.SemiAuto)],
);
});

test(`evaluate() is inapplicable when Applicability questions are unanswered`, async (t) => {
const target = <audio src="foo.mp3" />;

Expand Down

0 comments on commit f49666e

Please sign in to comment.