Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 78 additions & 4 deletions client-src/elements/chromedash-wpt-eval-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class ChromedashWPTEvalPage extends LitElement {
}

.requirement-item sl-icon {
font-size: 1.4em;
font-size: 20px;
flex-shrink: 0;
}
.requirement-item .success {
Expand Down Expand Up @@ -305,6 +305,9 @@ export class ChromedashWPTEvalPage extends LitElement {
completedInThisSession = false;

// Milliseconds remaining before another run can be requested
@state()
includeExplainer = false;

@state()
private _cooldownRemaining = 0;

Expand Down Expand Up @@ -464,7 +467,10 @@ export class ChromedashWPTEvalPage extends LitElement {
this.completedInThisSession = false; // Reset if user tries to regenerate.
this.managePolling();

await window.csClient.generateWPTCoverageEvaluation(this.featureId);
await window.csClient.generateWPTCoverageEvaluation(
this.featureId,
this.includeExplainer
);
} catch (e) {
showToastMessage('Failed to generate report. Please try again later.');
this.fetchData();
Expand Down Expand Up @@ -516,7 +522,12 @@ export class ChromedashWPTEvalPage extends LitElement {
library="material"
name="check_circle_20px"
></sl-icon>`
: html`<sl-icon name="x-circle-fill" class="danger"></sl-icon>`;
: html`<sl-icon
library="material"
name="cancel_20px"
class="danger"
style="font-size: 20px"
></sl-icon>`;

const text = isFulfilled ? `${label} provided` : `Missing ${label}`;

Expand Down Expand Up @@ -641,6 +652,63 @@ export class ChromedashWPTEvalPage extends LitElement {
</div>
`
: nothing}
<div class="requirement-item">
${!this.includeExplainer
? html`<sl-icon
library="material"
name="info_20px"
style="color: var(--sl-color-neutral-600); font-size: 20px"
></sl-icon>`
: this.feature.explainer_links?.length
? html`<sl-icon
class="success"
library="material"
name="check_circle_20px"
></sl-icon>`
: html`<sl-icon
library="material"
name="cancel_20px"
class="danger"
style="font-size: 20px"
></sl-icon>`}
<sl-checkbox
?checked=${this.includeExplainer}
@sl-change=${(e: any) =>
(this.includeExplainer = e.target.checked)}
>
Include feature explainers
${!this.feature.explainer_links?.length
? html`<sl-badge variant="neutral" size="small"
>Optional</sl-badge
>`
: nothing}
</sl-checkbox>
<a
class="edit-link"
href="/guide/editall/${this.featureId}#id_explainer_links"
>
Edit
</a>
</div>
${this.includeExplainer
? html`
<div class="url-list-container">
<ul class="url-list">
${this.feature.explainer_links?.length
? this.feature.explainer_links.map(
url => html`
<li>
<a href="${url}" target="_blank">${url}</a>
</li>
`
)
: html`<li>
None provided. Use the "Edit" link above to add one.
</li>`}
</ul>
</div>
`
: nothing}
</div>
</section>
`;
Expand Down Expand Up @@ -848,7 +916,7 @@ export class ChromedashWPTEvalPage extends LitElement {
</h1>

<sl-alert variant="primary" open>
<sl-icon slot="icon" name="info-circle"></sl-icon>
<sl-icon slot="icon" library="material" name="info_20px"></sl-icon>
This feature is experimental and reports may be inaccurate. Please
<a
href="https://github.com/GoogleChrome/chromium-dashboard/issues/new?labels=Feedback,WPT-AI"
Expand Down Expand Up @@ -883,6 +951,12 @@ export class ChromedashWPTEvalPage extends LitElement {
accurate. These will be used to narrow the scope of the test
coverage requirements.
</li>
<li>
<strong>Explainers (Optional):</strong> You can optionally include
explainer links to provide more context to Gemini. This helps
Gemini understand the intent and design of your feature, which can
lead to more accurate coverage analysis.
</li>
<li>
<strong>Test Results:</strong> Add relevant
<code>wpt.fyi</code> URLs to the
Expand Down
135 changes: 133 additions & 2 deletions client-src/elements/chromedash-wpt-eval-page_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ describe('chromedash-wpt-eval-page', () => {
deleteWPTCoverageEvaluation: sinon.stub(),
};
(window as any).csClient = csClientStub;
sinon.stub(ChromedashWPTEvalPage.prototype, 'managePolling');
sinon.stub(ChromedashWPTEvalPage.prototype, 'updateCooldown');
});

afterEach(() => {
Expand All @@ -51,11 +53,16 @@ describe('chromedash-wpt-eval-page', () => {
expect(el.shadowRoot!.querySelector('.experimental-tag')).to.exist;
expect(el.shadowRoot!.querySelector('sl-alert')).to.exist;
expect(el.shadowRoot!.querySelector('.description')).to.exist;
expect(
el.shadowRoot!.querySelector('.description')!.textContent
).to.contain('Explainers (Optional)');
});

it('shows skeletons while loading data', async () => {
// Return a promise that doesn't resolve immediately to test loading state
csClientStub.getFeature.returns(new Promise(() => {}));
csClientStub.getFeature.returns(
new Promise(resolve => setTimeout(resolve, 100))
);
const el = await fixture<ChromedashWPTEvalPage>(
html`<chromedash-wpt-eval-page
.featureId=${123}
Expand Down Expand Up @@ -158,6 +165,8 @@ describe('chromedash-wpt-eval-page', () => {
});

mockWriteText.reset();
sinon.stub(ChromedashWPTEvalPage.prototype, 'managePolling');
sinon.stub(ChromedashWPTEvalPage.prototype, 'updateCooldown');
});

it('renders the copy button with correct text', async () => {
Expand Down Expand Up @@ -267,6 +276,99 @@ describe('chromedash-wpt-eval-page', () => {
// Verify Valid URLs (Index 4)
expect(dataContainers[4].querySelector('ul')).to.exist;
});

it('renders explainer links row correctly when links are present', async () => {
const explainerLinks = ['https://example.com/explainer.md'];
csClientStub.getFeature.resolves({
...mockFeatureV1,
explainer_links: explainerLinks,
});

const el = await fixture<ChromedashWPTEvalPage>(
html`<chromedash-wpt-eval-page
.featureId=${1}
></chromedash-wpt-eval-page>`
);
await el.updateComplete;

const items = el.shadowRoot!.querySelectorAll('.requirement-item');
// Total 6 now: Name, Summary, Spec, Descr, Valid URLs, Explainers
expect(items.length).to.equal(6);

const explainerItem = items[5];
expect(explainerItem.querySelector('.success')).to.exist;
expect(explainerItem.querySelector('sl-checkbox')).to.exist;
expect(
explainerItem.querySelector('sl-checkbox')!.textContent
).to.contain('Include feature explainers');
expect(explainerItem.querySelector('sl-badge')).to.not.exist;

const urlList = el.shadowRoot!.querySelectorAll('.url-list');
// Index 4 is WPT URLs, Index 5 is Explainer URLs
expect(urlList[5].textContent).to.contain(explainerLinks[0]);
});

it('renders explainer links row correctly with Optional badge and info icon when links are missing and unchecked', async () => {
csClientStub.getFeature.resolves({
...mockFeatureV1,
explainer_links: [],
});

const el = await fixture<ChromedashWPTEvalPage>(
html`<chromedash-wpt-eval-page
.featureId=${1}
></chromedash-wpt-eval-page>`
);
await el.updateComplete;

const items = el.shadowRoot!.querySelectorAll('.requirement-item');
expect(items.length).to.equal(6);

const explainerItem = items[5];
// includeExplainer defaults to false, so it should show info icon if missing
expect(explainerItem.querySelector('.success')).to.not.exist;
expect(explainerItem.querySelector('sl-icon[name="info_20px"]')).to.exist;
expect(explainerItem.querySelector('sl-badge[variant="neutral"]')).to
.exist;
expect(explainerItem.querySelector('sl-badge')!.textContent).to.contain(
'Optional'
);

// Since includeExplainer is false, the list should not be rendered
const urlList = el.shadowRoot!.querySelectorAll('.url-list');
// Index 4 is WPT URLs. Index 5 (Explainers) should not exist.
expect(urlList.length).to.equal(5);
});

it('toggling the includeExplainer checkbox updates state and icons', async () => {
csClientStub.getFeature.resolves({
...mockFeatureV1,
explainer_links: [],
});
const el = await fixture<ChromedashWPTEvalPage>(
html`<chromedash-wpt-eval-page
.featureId=${1}
></chromedash-wpt-eval-page>`
);
await el.updateComplete;

expect(el.includeExplainer).to.be.false;
const items = el.shadowRoot!.querySelectorAll('.requirement-item');
expect(items[5].querySelector('sl-icon[name="info_20px"]')).to.exist;

const checkbox = el.shadowRoot!.querySelector('sl-checkbox') as any;
checkbox.click();
await el.updateComplete;
await new Promise(resolve => setTimeout(resolve, 0));

expect(el.includeExplainer).to.be.true;
expect(
el
.shadowRoot!.querySelectorAll('.requirement-item')[5]
.querySelector('sl-icon[name="cancel_20px"].danger')
).to.exist;
});

it('annotates directory WPT URLs but not individual test file URLs', async () => {
const dirUrl = 'https://wpt.fyi/results/css';
const fileUrl = 'https://wpt.fyi/results/dom/historical.html';
Expand Down Expand Up @@ -448,7 +550,7 @@ describe('chromedash-wpt-eval-page', () => {
// API called
expect(
csClientStub.generateWPTCoverageEvaluation
).to.have.been.calledWith(99);
).to.have.been.calledWith(99, false); // Default is false

// UI entered IN_PROGRESS state immediately (optimistic update)
expect(el.feature?.ai_test_eval_run_status).to.equal(
Expand All @@ -461,6 +563,33 @@ describe('chromedash-wpt-eval-page', () => {
expect(setIntervalSpy).to.have.been.called;
});

it('passes includeExplainer=true to API when checkbox is checked', async () => {
csClientStub.getFeature.resolves(mockFeatureV1);
csClientStub.generateWPTCoverageEvaluation.resolves({});

const el = await fixture<ChromedashWPTEvalPage>(
html`<chromedash-wpt-eval-page
.featureId=${99}
></chromedash-wpt-eval-page>`
);
await el.updateComplete;

// Check the checkbox
const checkbox = el.shadowRoot!.querySelector('sl-checkbox') as any;
checkbox.click();
await el.updateComplete;

const button = el.shadowRoot!.querySelector(
'.generate-button'
) as HTMLElement;
button.click();
await el.updateComplete;

expect(
csClientStub.generateWPTCoverageEvaluation
).to.have.been.calledWith(99, true);
});

it('renders IN_PROGRESS state when loaded from server', async () => {
csClientStub.getFeature.resolves({
...mockFeatureV1,
Expand Down Expand Up @@ -788,6 +917,8 @@ describe('chromedash-wpt-eval-page', () => {

beforeEach(() => {
confirmStub = sinon.stub(window, 'confirm');
sinon.stub(ChromedashWPTEvalPage.prototype, 'managePolling');
sinon.stub(ChromedashWPTEvalPage.prototype, 'updateCooldown');
});

it('renders the delete button when a report is present', async () => {
Expand Down
6 changes: 4 additions & 2 deletions client-src/js-src/cs-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,8 +775,10 @@ export class ChromeStatusClient {
}

// WPT Coverage API
async generateWPTCoverageEvaluation(featureId) {
return this.doPost(`/features/${featureId}/wpt-coverage-analysis`);
async generateWPTCoverageEvaluation(featureId, includeExplainer = false) {
return this.doPost(`/features/${featureId}/wpt-coverage-analysis`, {
include_explainer: includeExplainer,
});
}

// Delete WPT Coverage Report
Expand Down
1 change: 1 addition & 0 deletions static/shoelace/assets/material-icons/cancel_20px.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions static/shoelace/assets/material-icons/info_20px.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading