Skip to content

Commit

Permalink
Merge pull request #2437 from codecrafters-io/stage-2-prototype
Browse files Browse the repository at this point in the history
Stage 2 prototype
  • Loading branch information
andy1li authored Nov 29, 2024
2 parents b7ee6d1 + f4db679 commit 9470039
Show file tree
Hide file tree
Showing 15 changed files with 104 additions and 164 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<CoursePage::InstructionsCard @title="How to pass this stage" id="second-stage-tutorial-card" ...attributes>
<:content>
<div class="prose dark:prose-invert mb-5">
<p>
In this stage, you'll implement your own solution. Unlike stage 1, your repository doesn't contain commented code to pass this stage.
</p>

{{#unless (eq @repository.course.slug "shell")}}
<p>
In this stage, you'll implement your own solution. Unlike stage 1, your repository doesn't contain commented code to pass this stage.
</p>
{{/unless}}
<p>
{{! TODO: Aggregate stat across all stages, try to fetch challenge-specific values from the backend. }}
<b class="text-teal-600">98%</b>
Expand All @@ -13,19 +14,12 @@
</div>

<ExpandableStepList @steps={{this.steps}} @onManualStepComplete={{this.handleStepCompletedManually}} class="scroll-mt-32" as |stepList|>
{{#if (eq stepList.expandedStep.id "read-instructions")}}
<CoursePage::CourseStageStep::SecondStageTutorialCard::ReadInstructionsStep
@repository={{@repository}}
@courseStage={{@courseStage}}
@isComplete={{this.readInstructionsStepIsComplete}}
@shouldRecommendLanguageGuide={{@shouldRecommendLanguageGuide}}
/>
{{else if (eq stepList.expandedStep.id "implement-solution")}}
{{#if (eq stepList.expandedStep.id "implement-solution")}}
<CoursePage::CourseStageStep::SecondStageTutorialCard::ImplementSolutionStep
@repository={{@repository}}
@courseStage={{@courseStage}}
@isComplete={{this.implementSolutionStepIsComplete}}
@shouldRecommendLanguageGuide={{@shouldRecommendLanguageGuide}}
@languageGuide={{@languageGuide}}
@shouldShowSolution={{@shouldShowSolution}}
/>
{{else if (eq stepList.expandedStep.id "run-tests")}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
import CoursePageStateService from 'codecrafters-frontend/services/course-page-state';
import Store from '@ember-data/store';
import type RepositoryModel from 'codecrafters-frontend/models/repository';
import type CourseStageLanguageGuideModel from 'codecrafters-frontend/models/course-stage-language-guide';
import type CourseStageModel from 'codecrafters-frontend/models/course-stage';
import { action } from '@ember/object';
import type { Step } from 'codecrafters-frontend/components/expandable-step-list';
Expand All @@ -14,7 +15,7 @@ interface Signature {
Args: {
repository: RepositoryModel;
courseStage: CourseStageModel;
shouldRecommendLanguageGuide: boolean;
languageGuide?: CourseStageLanguageGuideModel;
shouldShowSolution: boolean;
};
}
Expand All @@ -29,15 +30,6 @@ class BaseStep {
}
}

class ReadInstructionsStep extends BaseStep implements Step {
id = 'read-instructions';
canBeCompletedManually = true;

get titleMarkdown() {
return 'Read instructions';
}
}

class ImplementSolutionStep extends BaseStep implements Step {
id = 'implement-solution';
canBeCompletedManually = true;
Expand Down Expand Up @@ -92,7 +84,6 @@ export default class SecondStageTutorialCardComponent extends Component<Signatur

get steps() {
return [
new ReadInstructionsStep(this.args.repository, this.readInstructionsStepIsComplete),
new ImplementSolutionStep(this.args.repository, this.implementSolutionStepIsComplete),
new RunTestsStep(this.args.repository, this.runTestsStepIsComplete),
];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
<p class="prose dark:prose-invert prose-compact mb-3">
Head over to your editor / IDE and implement your solution.
</p>
<div class="prose dark:prose-invert prose-compact mb-3">
{{#if (eq @repository.course.slug "shell")}}
{{markdown-to-html @courseStage.shortInstructionsMarkdown}}
{{/if}}

<p>
Head over to your editor / IDE and implement your solution.
</p>
</div>

{{#if (and @shouldShowSolution this.solution)}}
<BlurredOverlay
{{on "click" this.handleRevealSolutionButtonClick}}
class="mb-3 group"
@isBlurred={{this.solutionIsBlurred}}
@overlayClass="inset-px rounded cursor-pointer group-hover:backdrop-blur-[3px] group-hover:bg-gray-50/20 dark:bg-gray-900/20 dark:group-hover:bg-gray-900/0"
>
<:content>
<div class="flex flex-col gap-4">
<div class="grid gap-3 mb-3">
{{#each this.solution.changedFiles as |changedFile|}}
{{! Extra if condition convinces typescript that solution isn't null }}
{{#if this.solution}}
Expand All @@ -23,6 +28,27 @@
{{/if}}
{{/each}}
</div>

{{#unless this.solutionIsBlurred}}
<TertiaryButton
class="w-full mb-6 flex justify-center items-center gap-2 dark:bg-transparent dark:text-gray-200 dark:border-white/5 dark:hover:border-gray-700/60 dark:bg-gray-800 dark:hover:bg-gray-700/50"
{{on "click" this.handleHideSolutionButtonClick}}
data-test-hide-solution-button
>
{{svg-jar "eye-off" class="size-4"}}
Hide Solution
</TertiaryButton>

{{#if @languageGuide}}
<div
class="prose dark:prose-invert has-prism-highlighting"
{{highlight-code-blocks @languageGuide.markdownForBeginner}}
data-test-language-guide-card
>
{{markdown-to-html @languageGuide.markdownForBeginner}}
</div>
{{/if}}
{{/unless}}
</:content>

<:overlay>
Expand All @@ -35,18 +61,8 @@
<span>Click to reveal solution</span>
</div>
</SecondaryButton>
<button class="absolute inset-0" type="button" {{on "click" this.handleRevealSolutionButtonClick}} data-test-solution-blurred-overlay>
</button>
</:overlay>
</BlurredOverlay>
{{/if}}

<p class="prose dark:prose-invert prose-compact">
{{#if @shouldRecommendLanguageGuide}}
For a more detailed explanation on how this solution works, view the
<a href="#language-guide-card">{{@repository.language.name}} Guide</a>
card.
{{else}}
If you want a quick look at what functions to use or how to structure your code, we recommend looking at
<LinkTo @route="course.stage.code-examples">Code Examples</LinkTo>
tab.
{{/if}}
</p>
{{/if}}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Component from '@glimmer/component';
import type RepositoryModel from 'codecrafters-frontend/models/repository';
import type CourseStageModel from 'codecrafters-frontend/models/course-stage';
import type CourseStageLanguageGuideModel from 'codecrafters-frontend/models/course-stage-language-guide';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

Expand All @@ -11,7 +12,7 @@ interface Signature {
repository: RepositoryModel;
courseStage: CourseStageModel;
isComplete: boolean;
shouldRecommendLanguageGuide: boolean;
languageGuide?: CourseStageLanguageGuideModel;
shouldShowSolution: boolean;
};
}
Expand All @@ -23,6 +24,11 @@ export default class ImplementSolutionStepComponent extends Component<Signature>
return this.args.repository.secondStageSolution;
}

@action
handleHideSolutionButtonClick() {
this.solutionIsBlurred = true;
}

@action
handleRevealSolutionButtonClick() {
this.solution?.createView();
Expand Down
3 changes: 1 addition & 2 deletions app/components/expandable-step-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ export default class ExpandableStepListComponent extends Component<Signature> {
super(owner, args);

const firstIncompleteStep = this.firstIncompleteStep;
const firstStep = this.args.steps[0];

if (firstIncompleteStep && firstStep && firstIncompleteStep.id !== firstStep.id) {
if (firstIncompleteStep) {
this.expandedStepId = firstIncompleteStep.id;
}
}
Expand Down
27 changes: 1 addition & 26 deletions app/components/expandable-step-list/step.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,10 @@
{{svg-jar "check-circle" class="w-6 h-6 text-teal-500"}}
</div>
{{/if}}

<div class="flex ml-2">
{{#if (and @isExpanded @step.isComplete @nextIncompleteStep)}}
<PrimaryButton {{on "click" @onCollapse}} @size="extra-small">
<div class="flex items-center gap-1">
{{svg-jar "arrow-down" class="w-3 h-3"}}
<span>Next Step</span>
</div>
</PrimaryButton>
{{else if (and (not @isExpanded) @isFirstIncompleteStep)}}
<PrimaryButton @size="extra-small" data-test-expand-step-button>
<div class="font-bold flex items-center gap-1">
{{svg-jar "arrow-down" class="w-3 fill-current"}}
Expand
</div>
</PrimaryButton>
{{/if}}
</div>
</div>

<div>
{{#if (and @isExpanded (not @step.isComplete) @step.canBeCompletedManually)}}
<PrimaryButton {{on "click" @onManualComplete}} @size="extra-small">
<div class="flex items-center gap-1">
{{svg-jar "check-circle" class="w-4 h-4"}}
<span>Mark as complete</span>
</div>
</PrimaryButton>
{{else if (and @isExpanded @step.isComplete (not @nextIncompleteStep))}}
{{#if (and @isExpanded @step.isComplete (not @nextIncompleteStep))}}
<TertiaryButton @size="extra-small" {{on "click" @onCollapse}}>
<div class="flex items-center gap-1">
{{svg-jar "arrow-up" class="w-3 h-3"}}
Expand Down
4 changes: 0 additions & 4 deletions app/controllers/course/stage/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ export default class CourseStageInstructionsController extends Controller {
return !this.currentStep.courseStage.isFirst && this.currentStep.status === 'complete';
}

get shouldShowLanguageGuide() {
return !this.model.courseStage.isFirst && !!this.languageGuide;
}

get shouldShowPrerequisites() {
return !!this.prerequisiteInstructionsMarkdown;
}
Expand Down
13 changes: 13 additions & 0 deletions app/models/course-stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,19 @@ Our interactive concepts can help with this:
return this.course.extensions.filter((extension) => this.secondaryExtensionSlugs.includes(extension.slug));
}

get shortInstructionsMarkdown() {
return `
In this stage, you'll implement support for handling invalid commands in your shell.
Example:
\`\`\`
$ invalid_command
invalid_command: not found
\`\`\`
`;
}

get solutionIsAccessibleToMembersOnly() {
return this.position > 3;
}
Expand Down
9 changes: 1 addition & 8 deletions app/templates/course/stage/instructions.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<CoursePage::CourseStageStep::SecondStageTutorialCard
@repository={{@model.activeRepository}}
@courseStage={{@model.courseStage}}
@shouldRecommendLanguageGuide={{this.shouldShowLanguageGuide}}
@languageGuide={{this.languageGuide}}
@shouldShowSolution={{this.shouldShowStage2Solution}}
class="mb-6"
/>
Expand Down Expand Up @@ -86,13 +86,6 @@

<CoursePage::CourseStageStep::YourTaskCard @repository={{@model.activeRepository}} @courseStage={{@model.courseStage}} />

{{#if this.shouldShowLanguageGuide}}
{{! Extra if condition convinces typescript that languageGuide isn't null }}
{{#if this.languageGuide}}
<CoursePage::CourseStageStep::SimpleLanguageGuideCard @languageGuide={{this.languageGuide}} class="mt-6" />
{{/if}}
{{/if}}

<div data-percy-hints-section>
<div class="border-b dark:border-white/5 pb-2 mb-6 mt-8 flex items-center justify-between">
<h2 class="font-semibold text-lg text-gray-800 dark:text-gray-200">Hints</h2>
Expand Down
10 changes: 1 addition & 9 deletions tests/acceptance/course-page/complete-first-stage-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,23 @@ module('Acceptance | course-page | complete-first-stage', function (hooks) {
await coursePage.firstStageTutorialCard.scrollIntoView();

assert.notOk(coursePage.firstStageTutorialCard.steps[0].isComplete, 'First step is not complete');
assert.notOk(coursePage.firstStageTutorialCard.steps[0].isExpanded, 'First step is not expanded');
assert.ok(coursePage.firstStageTutorialCard.steps[0].isExpanded, 'First step is expanded');
assert.notOk(coursePage.firstStageTutorialCard.steps[1].isComplete, 'Second step is not complete');
assert.notOk(coursePage.firstStageTutorialCard.steps[1].isExpanded, 'Second step is not expanded');
assert.notOk(coursePage.firstStageTutorialCard.steps[2].isComplete, 'Third step is not complete');
assert.notOk(coursePage.firstStageTutorialCard.steps[2].isExpanded, 'Third step is not expanded');

await coursePage.firstStageTutorialCard.steps[0].click();

assert.notOk(coursePage.firstStageTutorialCard.steps[0].isComplete, 'First step is not complete');
assert.ok(coursePage.firstStageTutorialCard.steps[0].isExpanded, 'First step is expanded');
assert.notOk(coursePage.firstStageTutorialCard.steps[1].isComplete, 'Second step is not complete');
assert.notOk(coursePage.firstStageTutorialCard.steps[1].isExpanded, 'Second step is not expanded');
assert.notOk(coursePage.firstStageTutorialCard.steps[2].isComplete, 'Third step is not complete');
assert.notOk(coursePage.firstStageTutorialCard.steps[2].isExpanded, 'Third step is not expanded');

await coursePage.firstStageTutorialCard.clickOnCompleteStepButton();

assert.ok(coursePage.firstStageTutorialCard.steps[0].isComplete, 'First step is complete');
assert.notOk(coursePage.firstStageTutorialCard.steps[0].isExpanded, 'First step is collapsed');
assert.notOk(coursePage.firstStageTutorialCard.steps[1].isComplete, 'Second step is not complete');
assert.ok(coursePage.firstStageTutorialCard.steps[1].isExpanded, 'Second step is expanded');
assert.notOk(coursePage.firstStageTutorialCard.steps[2].isComplete, 'Third step is not complete');
assert.notOk(coursePage.firstStageTutorialCard.steps[2].isExpanded, 'Third step is not expanded');

await coursePage.firstStageTutorialCard.clickOnCompleteStepButton();

Expand All @@ -78,8 +72,6 @@ module('Acceptance | course-page | complete-first-stage', function (hooks) {
assert.notOk(coursePage.firstStageTutorialCard.steps[0].isExpanded, 'First step is collapsed');
assert.ok(coursePage.firstStageTutorialCard.steps[1].isComplete, 'Second step is complete');
assert.notOk(coursePage.firstStageTutorialCard.steps[1].isExpanded, 'Second step is collapsed');
assert.notOk(coursePage.firstStageTutorialCard.steps[2].isExpanded, 'Third step is collapsed');
assert.ok(coursePage.firstStageTutorialCard.steps[2].isComplete, 'Third step is complete');

await coursePage.testRunnerCard.clickOnMarkStageAsCompleteButton();

Expand Down
21 changes: 8 additions & 13 deletions tests/acceptance/course-page/complete-second-stage-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@ module('Acceptance | course-page | complete-second-stage', function (hooks) {
await catalogPage.clickOnCourse('Build your own Dummy');

assert.notOk(coursePage.secondStageTutorialCard.steps[0].isComplete, 'First step is not complete');
assert.notOk(coursePage.secondStageTutorialCard.steps[0].isExpanded, 'First step is not expanded');
assert.ok(coursePage.secondStageTutorialCard.steps[0].isExpanded, 'First step is expanded');
assert.notOk(coursePage.secondStageTutorialCard.steps[1].isComplete, 'Second step is not complete');
assert.notOk(coursePage.secondStageTutorialCard.steps[1].isExpanded, 'Second step is not expanded');
assert.notOk(coursePage.secondStageTutorialCard.steps[2].isComplete, 'Third step is not complete');
assert.notOk(coursePage.secondStageTutorialCard.steps[2].isExpanded, 'Third step is not expanded');

await coursePage.secondStageTutorialCard.steps[0].click();

Expand All @@ -54,7 +52,7 @@ module('Acceptance | course-page | complete-second-stage', function (hooks) {

// TODO: See if we can retain expanded/collapsed state after switching tabs?
assert.notOk(coursePage.secondStageTutorialCard.steps[0].isComplete, 'First step is not complete');
assert.notOk(coursePage.secondStageTutorialCard.steps[0].isExpanded, 'First step is collapsed');
assert.ok(coursePage.secondStageTutorialCard.steps[0].isExpanded, 'First step is expanded');

await coursePage.secondStageTutorialCard.steps[0].click();
await coursePage.secondStageTutorialCard.clickOnCompleteStepButton();
Expand All @@ -71,18 +69,18 @@ module('Acceptance | course-page | complete-second-stage', function (hooks) {
assert.notOk(coursePage.secondStageTutorialCard.steps[1].isComplete, 'Second step is not complete');
assert.ok(coursePage.secondStageTutorialCard.steps[1].isExpanded, 'Second step is expanded');

await coursePage.secondStageTutorialCard.clickOnCompleteStepButton();
await coursePage.secondStageTutorialCard.steps[0].click();

assert.ok(coursePage.secondStageTutorialCard.steps[0].isComplete, 'First step is complete');
assert.ok(coursePage.secondStageTutorialCard.steps[1].isComplete, 'Second step is complete');
assert.notOk(coursePage.secondStageTutorialCard.steps[2].isComplete, 'Third step is not complete');
assert.notOk(coursePage.secondStageTutorialCard.steps[1].isComplete, 'Second step is not complete');

assert.notOk(coursePage.secondStageTutorialCard.steps[0].isExpanded, 'First step is collapsed');
assert.ok(coursePage.secondStageTutorialCard.steps[0].isExpanded, 'First step is expanded');
assert.notOk(coursePage.secondStageTutorialCard.steps[1].isExpanded, 'Second step is collapsed');
assert.ok(coursePage.secondStageTutorialCard.steps[2].isExpanded, 'Third step is expanded');

await coursePage.secondStageTutorialCard.steps[1].click();

// Asserts that we don't show the "To run tests again..." message for a system submission
assert.contains(coursePage.secondStageTutorialCard.steps[2].instructions, 'To run tests, make changes to your code');
assert.contains(coursePage.secondStageTutorialCard.steps[1].instructions, 'To run tests, make changes to your code');

this.server.create('submission', 'withSuccessStatus', {
repository: repository,
Expand All @@ -94,10 +92,8 @@ module('Acceptance | course-page | complete-second-stage', function (hooks) {

assert.ok(coursePage.secondStageTutorialCard.steps[0].isComplete, 'First step is complete');
assert.ok(coursePage.secondStageTutorialCard.steps[1].isComplete, 'Second step is complete');
assert.ok(coursePage.secondStageTutorialCard.steps[2].isComplete, 'Third step is complete');
assert.notOk(coursePage.secondStageTutorialCard.steps[0].isExpanded, 'First step is collapsed');
assert.notOk(coursePage.secondStageTutorialCard.steps[1].isExpanded, 'Second step is collapsed');
assert.notOk(coursePage.secondStageTutorialCard.steps[2].isExpanded, 'Third step is collapsed');

assert.ok(coursePage.testRunnerCard.isExpanded, 'Test runner card is expanded');
await coursePage.testRunnerCard.clickOnMarkStageAsCompleteButton();
Expand Down Expand Up @@ -136,7 +132,6 @@ module('Acceptance | course-page | complete-second-stage', function (hooks) {

assert.ok(coursePage.secondStageTutorialCard.steps[0].isComplete, 'First step is complete');
assert.ok(coursePage.secondStageTutorialCard.steps[1].isComplete, 'Second step is complete');
assert.ok(coursePage.secondStageTutorialCard.steps[2].isComplete, 'Third step is complete');

assert.ok(coursePage.testRunnerCard.isExpanded, 'Test runner card is expanded');
assert.notOk(coursePage.testRunnerCard.markStageAsCompleteButton.isVisible, 'Mark stage as complete button is not visible');
Expand Down
Loading

0 comments on commit 9470039

Please sign in to comment.