Skip to content

Commit

Permalink
Add a static loading state when fetching Compose result
Browse files Browse the repository at this point in the history
- Inline a SVG of the loading state for now
- Update refresh button so that clicking on fetches another result
- Only allow re-editing input when loading is complete

http://screen/3mGbKH55JNQrbRh

Bug: b:302192206
Change-Id: I8b12d733a25665719e940f2625b594117f667672
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4932904
Reviewed-by: Anthony Cui <cuianthony@chromium.org>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Commit-Queue: John Lee <johntlee@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1209069}
  • Loading branch information
John Lee authored and Chromium LUCI CQ committed Oct 12, 2023
1 parent 021b61c commit c40f56d
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 48 deletions.
24 changes: 20 additions & 4 deletions chrome/browser/resources/compose/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
padding: 0 20px;
}

#loading {
display: flex;
border: solid 1px var(--color-compose-dialog-result-background);
border-radius: 8px;
padding: 16px;
}

#resultContainer {
border-radius: 8px;
padding: 16px;
Expand Down Expand Up @@ -113,9 +120,18 @@ <h1>Autofill</h1>
<compose-textarea id="textarea"
on-value-changed="onTextareaValueChanged_"
readonly="[[submitted_]]"
allow-exiting-readonly-mode="[[submitted_]]">
allow-exiting-readonly-mode="[[!loading_]]">
</compose-textarea>

<div id="loading" hidden="[[!loading_]]">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="43">
<rect x="0" y="0" width="100%" height="11" rx="4" fill="#D9D9D9"></rect>
<rect x="0" y="16" width="100%" height="10.8333" rx="4" fill="#D9D9D9">
</rect>
<rect x="0" y="32" width="75%" height="11" rx="4" fill="#D9D9D9"></rect>
</svg>
</div>

<div id="resultContainer" hidden="[[!result_]]">
[[result_]]

Expand All @@ -124,12 +140,12 @@ <h1>Autofill</h1>
<select class="md-select"><option>Menu 2</option></select>

<div class="icon-buttons-row">
<cr-icon-button id="undo" iron-icon="cr:arrow-back"
<cr-icon-button id="undoButton" iron-icon="cr:arrow-back"
disabled="[[!undoEnabled_]]"
on-click="onUndoClick_">
</cr-icon-button>
<cr-icon-button id="refresh" iron-icon="cr:sync"
on-click="onRefreshClick_">
<cr-icon-button id="refreshButton" iron-icon="cr:sync"
on-click="onSubmit_">
</cr-icon-button>
</div>
</div>
Expand Down
58 changes: 34 additions & 24 deletions chrome/browser/resources/compose/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,15 @@ import {CrButtonElement} from '//resources/cr_elements/cr_button/cr_button.js';
import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {getTemplate} from './app.html.js';
import {Length, StyleModifiers, Tone} from './compose.mojom-webui.js';
import {Length, Tone} from './compose.mojom-webui.js';
import {ComposeApiProxy, ComposeApiProxyImpl} from './compose_api_proxy.js';
import {ComposeTextareaElement} from './textarea.js';

// Mock code.
function generateRandomText(): string {
const randomNumber = Math.random() * (200 - 50) + 50;
const result: string[] = [];
for (let i = 0; i < randomNumber; i++) {
result.push('text');
}
return result.join(' ');
}

export interface ComposeAppElement {
$: {
insertButton: CrButtonElement,
loading: HTMLElement,
refreshButton: HTMLElement,
resultContainer: HTMLElement,
submitButton: CrButtonElement,
textarea: ComposeTextareaElement,
Expand All @@ -53,10 +45,22 @@ export class ComposeAppElement extends PolymerElement {
type: Boolean,
value: false,
},
loading_: {
type: Boolean,
value: false,
},
result_: {
type: String,
value: '',
},
selectedLength_: {
type: Number,
value: Length.kUnset,
},
selectedTone_: {
type: Number,
value: Tone.kUnset,
},
submitted_: {
type: Boolean,
value: false,
Expand All @@ -71,7 +75,10 @@ export class ComposeAppElement extends PolymerElement {
private apiProxy_: ComposeApiProxy = ComposeApiProxyImpl.getInstance();
private input_: string;
private isSubmitEnabled_: boolean;
private result_: string;
private loading_: boolean;
private result_: string|undefined;
private selectedLength_: Length;
private selectedTone_: Tone;
private submitted_: boolean;
private undoEnabled_: boolean;

Expand All @@ -80,29 +87,32 @@ export class ComposeAppElement extends PolymerElement {
ColorChangeUpdater.forDocument().start();
}

private onRefreshClick_() {
this.result_ = generateRandomText();
}

private async onSubmit_() {
private onSubmit_() {
if (!this.$.textarea.validate()) {
return;
}

this.submitted_ = true;
const styleModifiers:
StyleModifiers = {tone: Tone.kUnset, length: Length.kUnset};
const composeResult =
await this.apiProxy_.compose(styleModifiers, this.input_);

// TODO(b/302742291) store the error if any as well.
this.result_ = composeResult.result || 'error';
this.compose_();
}

private onTextareaValueChanged_() {
this.input_ = this.$.textarea.value;
this.isSubmitEnabled_ = this.$.textarea.validate();
}

private async compose_() {
this.loading_ = true;
this.result_ = undefined;
const response = await this.apiProxy_.compose(
{
length: this.selectedLength_,
tone: this.selectedTone_,
},
this.input_);
this.result_ = response.result || 'error';
this.loading_ = false;
}
}

declare global {
Expand Down
2 changes: 1 addition & 1 deletion chrome/browser/resources/compose/textarea.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
#editButtonContainer {
--cr-icon-button-icon-size: var(--cr-icon-size);
width: var(--cr-icon-size);
height: var(--cr-icon-size);
height: 16px;
display: flex;
align-items: center;
justify-content: center;
Expand Down
77 changes: 58 additions & 19 deletions chrome/test/data/webui/compose/compose_app_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,39 @@
import 'chrome://compose/app.js';

import {ComposeAppElement} from 'chrome://compose/app.js';
import {ComposeResponse, ComposeStatus, StyleModifiers} from 'chrome://compose/compose.mojom-webui.js';
import {ComposeResponse, ComposeStatus, Length, StyleModifiers, Tone} from 'chrome://compose/compose.mojom-webui.js';
import {ComposeApiProxy, ComposeApiProxyImpl} from 'chrome://compose/compose_api_proxy.js';
import {assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {isVisible, whenCheck} from 'chrome://webui-test/test_util.js';
import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
import {isVisible} from 'chrome://webui-test/test_util.js';


class TestingApiProxy implements ComposeApiProxy {
constructor() {}
class TestingApiProxy extends TestBrowserProxy implements ComposeApiProxy {
private composeOutput_: string = 'Some output';

compose(_style: StyleModifiers, _input: string): Promise<ComposeResponse> {
constructor() {
super(['compose']);
}

compose(style: StyleModifiers, input: string): Promise<ComposeResponse> {
this.methodCalled('compose', {style, input});
return Promise.resolve(
{status: ComposeStatus.kOk, result: 'Here is my output.'});
{status: ComposeStatus.kOk, result: this.composeOutput_});
}

setComposeOutput(output: string) {
this.composeOutput_ = output;
}
}

suite('ComposeApp', () => {
let app: ComposeAppElement;
let testProxy: TestingApiProxy;

setup(() => {
ComposeApiProxyImpl.setInstance(new TestingApiProxy());
testProxy = new TestingApiProxy();
ComposeApiProxyImpl.setInstance(testProxy);

document.body.innerHTML = window.trustedTypes!.emptyHTML;
app = document.createElement('compose-app');
Expand All @@ -34,6 +46,11 @@ suite('ComposeApp', () => {
return flushTasks();
});

function mockInput(input: string) {
app.$.textarea.value = input;
app.$.textarea.dispatchEvent(new CustomEvent('value-changed'));
}

test('SubmitsInput', async () => {
// Starts off with submit disabled since input is empty.
assertTrue(isVisible(app.$.submitButton));
Expand All @@ -42,28 +59,50 @@ suite('ComposeApp', () => {
assertFalse(isVisible(app.$.insertButton));

// Invalid input keeps submit disabled.
app.$.textarea.value = 'Short';
app.$.textarea.dispatchEvent(new CustomEvent('value-changed'));
mockInput('Short');
assertTrue(app.$.submitButton.disabled);

// Inputting valid text enables submit.
app.$.textarea.value = 'Here is my input.';
app.$.textarea.dispatchEvent(new CustomEvent('value-changed'));
mockInput('Here is my input.');
assertFalse(app.$.submitButton.disabled);

// Clicking on submit gets results.
app.$.submitButton.click();
assertTrue(isVisible(app.$.loading));

// Wait for api proxy promise to resolve.
await flushTasks();

const resultContainerVisible = whenCheck(app, () => {
return !app.$.resultContainer.hidden;
});
await resultContainerVisible;
const args = await testProxy.whenCalled('compose');
assertEquals(Length.kUnset, args.style.length);
assertEquals(Tone.kUnset, args.style.tone);
assertEquals('Here is my input.', args.input);

assertFalse(isVisible(app.$.loading));
assertFalse(isVisible(app.$.submitButton));
assertTrue(app.$.textarea.readonly);
assertTrue(isVisible(app.$.insertButton));
});

test('RefreshesResult', async () => {
// Submit the input once so the refresh button shows up.
testProxy.setComposeOutput('Outdated output.');
mockInput('Input to refresh.');
app.$.submitButton.click();
await testProxy.whenCalled('compose');
testProxy.resetResolver('compose');
assertTrue(isVisible(app.$.refreshButton));

// Click the refresh button and assert compose is called with the same args.
testProxy.setComposeOutput('Refreshed output.');
app.$.refreshButton.click();
assertTrue(isVisible(app.$.loading));
const args = await testProxy.whenCalled('compose');
assertEquals(Length.kUnset, args.style.length);
assertEquals(Tone.kUnset, args.style.tone);
assertEquals('Input to refresh.', args.input);

// Verify UI has updated with refreshed results.
assertFalse(isVisible(app.$.loading));
assertTrue(isVisible(app.$.resultContainer));
assertTrue(
app.$.resultContainer.textContent!.includes('Refreshed output.'));
});
});

0 comments on commit c40f56d

Please sign in to comment.