Skip to content

Commit 10ae2f0

Browse files
authored
Merge pull request #3082 from codecrafters-io/andy/fix-3
Fix DangerButtonWithTimedConfirmation on mobile
2 parents c710507 + b4b6466 commit 10ae2f0

File tree

6 files changed

+81
-47
lines changed

6 files changed

+81
-47
lines changed
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
<DangerButton
22
class="relative border-0 overflow-hidden"
3-
{{on "mouseenter" this.handleMouseEnter}}
4-
{{on "mouseleave" this.handleMouseLeave}}
53
{{! template-lint-disable no-pointer-down-event-binding }}
64
{{on "mousedown" this.startProgress}}
7-
{{on "mouseup" this.stopProgress}}
5+
{{on "mouseleave" this.resetProgress}}
6+
{{on "mouseup" this.resetProgress}}
7+
{{on "touchstart" this.startProgress}}
8+
{{on "touchcancel" this.resetProgress}}
9+
{{on "touchend" this.resetProgress}}
810
...attributes
911
>
1012
{{yield}}
1113

12-
{{#if this.isHovered}}
14+
{{#if this.shouldShowProgressBar}}
1315
<div class="absolute inset-0 bg-red-100 opacity-50" style={{this.progressBarWidthStyle}} data-test-progress-indicator></div>
1416
{{/if}}
1517
</DangerButton>

app/components/danger-button-with-timed-confirmation.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ interface Signature {
1818
}
1919

2020
export default class DangerButtonWithTimedConfirmation extends Component<Signature> {
21-
@tracked isHovered: boolean = false;
21+
@tracked shouldShowProgressBar: boolean = false;
2222
@tracked progressWidth: number = 0;
2323
@tracked progressInterval: number | undefined = undefined;
2424

@@ -27,26 +27,19 @@ export default class DangerButtonWithTimedConfirmation extends Component<Signatu
2727
}
2828

2929
@action
30-
handleMouseEnter() {
31-
this.isHovered = true;
32-
}
33-
34-
@action
35-
handleMouseLeave() {
36-
this.hideProgressBar();
37-
this.stopProgress();
30+
resetProgress() {
31+
this.shouldShowProgressBar = false;
32+
this.progressWidth = 0;
33+
clearInterval(this.progressInterval);
3834
}
3935

4036
@action
41-
hideProgressBar() {
42-
this.isHovered = false;
43-
}
37+
startProgress(event: Event) {
38+
event.preventDefault(); // Prevent menu from popping up, which triggers touchend
39+
this.resetProgress();
4440

45-
@action
46-
startProgress() {
41+
this.shouldShowProgressBar = true;
4742
const intervalDelay = config.environment === 'test' ? 1 : 30;
48-
this.progressWidth = 0;
49-
5043
this.progressInterval = setInterval(() => {
5144
if (this.progressWidth < 100) {
5245
this.progressWidth += 1;
@@ -59,12 +52,6 @@ export default class DangerButtonWithTimedConfirmation extends Component<Signatu
5952
}
6053
}, intervalDelay);
6154
}
62-
63-
@action
64-
stopProgress() {
65-
this.progressWidth = 0;
66-
clearInterval(this.progressInterval);
67-
}
6855
}
6956

7057
declare module '@glint/environment-ember-loose/registry' {

tests/acceptance/concept-admin/delete-concept-test.js

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,10 @@ module('Acceptance | concept-admin | delete-concept-test', function (hooks) {
3434
await basicDetailsPage.deleteMyConceptButton.hover();
3535
assertTooltipNotRendered(assert, 'tooltip is not rendered on hover');
3636

37-
await basicDetailsPage.deleteConceptModal.deleteConceptButton.hover();
37+
await basicDetailsPage.deleteConceptModal.deleteConceptButton.mousedown();
3838
assert.ok(basicDetailsPage.deleteConceptModal.deleteConceptButton.progressIndicator.isVisible, 'progress indicator should be visible');
39+
await waitUntil(() => basicDetailsPage.deleteConceptModal.deleteConceptButton.progressIndicator.width > 0, { timeout: 10 });
3940

40-
await basicDetailsPage.deleteConceptModal.deleteConceptButton.leave();
41-
assert.notOk(basicDetailsPage.deleteConceptModal.deleteConceptButton.progressIndicator.isVisible, 'progress indicator should not be visible');
42-
43-
await basicDetailsPage.deleteConceptModal.deleteConceptButton.press();
4441
await waitUntil(() => currentURL() === '/concepts');
4542
await settled(); // Delete request triggers after redirect
4643
assert.strictEqual(conceptsPage.conceptCards.length, 0, 'Concept is deleted');
@@ -73,13 +70,10 @@ module('Acceptance | concept-admin | delete-concept-test', function (hooks) {
7370

7471
await percySnapshot('Concept Admin - Delete Concept Modal');
7572

76-
await basicDetailsPage.deleteConceptModal.deleteConceptButton.hover();
73+
await basicDetailsPage.deleteConceptModal.deleteConceptButton.mousedown();
7774
assert.ok(basicDetailsPage.deleteConceptModal.deleteConceptButton.progressIndicator.isVisible, 'progress indicator should be visible');
75+
await waitUntil(() => basicDetailsPage.deleteConceptModal.deleteConceptButton.progressIndicator.width > 0, { timeout: 10 });
7876

79-
await basicDetailsPage.deleteConceptModal.deleteConceptButton.leave();
80-
assert.notOk(basicDetailsPage.deleteConceptModal.deleteConceptButton.progressIndicator.isVisible, 'progress indicator should not be visible');
81-
82-
await basicDetailsPage.deleteConceptModal.deleteConceptButton.press();
8377
await waitUntil(() => currentURL() === '/concepts');
8478
await settled(); // Delete request triggers after redirect
8579
assert.strictEqual(conceptsPage.conceptCards.length, 0, 'Concept is deleted');

tests/acceptance/course-page/delete-repository-test.js

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ module('Acceptance | course-page | delete-repository-test', function (hooks) {
126126
);
127127
});
128128

129-
test('can delete repository', async function (assert) {
129+
test('can delete repository via mouse down', async function (assert) {
130130
testScenario(this.server, ['dummy']);
131131
signInAsStaff(this.owner, this.server);
132132

@@ -150,13 +150,46 @@ module('Acceptance | course-page | delete-repository-test', function (hooks) {
150150

151151
await percySnapshot('Course Stages - Delete Repository Modal');
152152

153-
await coursePage.deleteRepositoryModal.deleteRepositoryButton.hover();
153+
await coursePage.deleteRepositoryModal.deleteRepositoryButton.mousedown();
154154
assert.ok(coursePage.deleteRepositoryModal.deleteRepositoryButton.progressIndicator.isVisible, 'progress indicator should be visible');
155+
await waitUntil(() => coursePage.deleteRepositoryModal.deleteRepositoryButton.progressIndicator.width > 0, { timeout: 10 });
155156

156-
await coursePage.deleteRepositoryModal.deleteRepositoryButton.leave();
157-
assert.notOk(coursePage.deleteRepositoryModal.deleteRepositoryButton.progressIndicator.isVisible, 'progress indicator should not be visible');
157+
await waitUntil(() => currentURL() === '/courses/dummy/introduction?repo=new&track=python');
158+
await settled(); // Delete request triggers after redirect
159+
160+
await coursePage.repositoryDropdown.click();
161+
assert.strictEqual(coursePage.repositoryDropdown.content.nonActiveRepositoryCount, 0, 'no repositories should be available');
162+
assert.notOk(coursePage.repositoryDropdown.content.text.includes('Delete Repository'), 'delete repository action should not be available');
163+
});
164+
165+
test('can delete repository via touch start', async function (assert) {
166+
testScenario(this.server, ['dummy']);
167+
signInAsStaff(this.owner, this.server);
168+
169+
let currentUser = this.server.schema.users.first();
170+
let python = this.server.schema.languages.findBy({ name: 'Python' });
171+
let course = this.server.schema.courses.findBy({ slug: 'dummy' });
172+
173+
course.update({ releaseStatus: 'live' });
174+
175+
this.server.create('repository', 'withFirstStageCompleted', {
176+
course: course,
177+
language: python,
178+
user: currentUser,
179+
});
180+
181+
await catalogPage.visit();
182+
await catalogPage.clickOnCourse('Build your own Dummy');
183+
await courseOverviewPage.clickOnStartCourse();
184+
await coursePage.repositoryDropdown.click();
185+
await coursePage.repositoryDropdown.clickOnAction('Delete Repository');
186+
187+
await percySnapshot('Course Stages - Delete Repository Modal');
188+
189+
await coursePage.deleteRepositoryModal.deleteRepositoryButton.touchstart();
190+
assert.ok(coursePage.deleteRepositoryModal.deleteRepositoryButton.progressIndicator.isVisible, 'progress indicator should be visible');
191+
await waitUntil(() => coursePage.deleteRepositoryModal.deleteRepositoryButton.progressIndicator.width > 0, { timeout: 10 });
158192

159-
await coursePage.deleteRepositoryModal.deleteRepositoryButton.press();
160193
await waitUntil(() => currentURL() === '/courses/dummy/introduction?repo=new&track=python');
161194
await settled(); // Delete request triggers after redirect
162195

tests/pages/concept-admin/basic-details-page.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@ export default create({
1919

2020
deleteConceptModal: {
2121
deleteConceptButton: {
22-
hover: triggerable('mouseenter'),
23-
leave: triggerable('mouseleave'),
24-
press: triggerable('mousedown'),
22+
mouseleave: triggerable('mouseleave'),
23+
mousedown: triggerable('mousedown'),
24+
touchstart: triggerable('touchstart'),
2525

2626
progressIndicator: {
2727
scope: '[data-test-progress-indicator]',
28+
get width() {
29+
const element = document.querySelector(this.scope) as HTMLElement;
30+
if (!element) return 0;
31+
32+
const styleAttr = element.getAttribute('style');
33+
const widthMatch = styleAttr?.match(/width:\s*(\d+)%/);
34+
35+
return widthMatch ? parseInt(widthMatch[1] as string) : 0;
36+
},
2837
},
2938

3039
release: triggerable('mouseup'),

tests/pages/course-page.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,21 @@ export default create({
8989
},
9090

9191
deleteRepositoryButton: {
92-
hover: triggerable('mouseenter'),
93-
leave: triggerable('mouseleave'),
94-
press: triggerable('mousedown'),
92+
mouseleave: triggerable('mouseleave'),
93+
mousedown: triggerable('mousedown'),
94+
touchstart: triggerable('touchstart'),
9595

9696
progressIndicator: {
9797
scope: '[data-test-progress-indicator]',
98+
get width() {
99+
const element = document.querySelector(this.scope) as HTMLElement;
100+
if (!element) return 0;
101+
102+
const styleAttr = element.getAttribute('style');
103+
const widthMatch = styleAttr?.match(/width:\s*(\d+)%/);
104+
105+
return widthMatch ? parseInt(widthMatch[1] as string) : 0;
106+
},
98107
},
99108

100109
release: triggerable('mouseup'),

0 commit comments

Comments
 (0)