diff --git a/dom-merge-conflict/buttons-and-counter/instructions.md b/dom-merge-conflict/buttons-and-counter/instructions.md new file mode 100644 index 0000000..2135d3c --- /dev/null +++ b/dom-merge-conflict/buttons-and-counter/instructions.md @@ -0,0 +1,52 @@ +# Buttons and Counter + +This app currently features a button that increments a counter. + +## Task for Person A + +Create and switch to a new branch named `split-app-components`: + +``` +git switch -c split-app-components +``` + +### Task + +Split the `header`, `main` into separate components from `app.js`. + +- Separate the components into new functions that live in their own files. +- Make sure to use those functions in `app.js` so the end result remains the same, and tests still run. +- Remember - tests are your friend, run them before, during and after you've wrote some code. Use them to guide you. + +### Tests + +There are three tests that are _not_ skipped (lines 18 - 36 ihn `app.test.js`), use these tests to check your changes still let the app function as expected. + +### Result + +- You should end up with an `app.js` file, a `header.js` file, and a `main.js` file in your `src` directory. +- All tests should pass. + +## Task for Person B + +Create and switch to a new branch named `add-decrement-button`: + +``` +git switch -c add-decrement-button +``` + +### Task + +Implement a decrement button. + +- Read the skipped tests (lines 38-53 in `app.test.js`) before you start. You will use these tests to guide you. +- Write your code in `app.js` +- Update the description of the app in the `header` element. + +### Tests + +Tests (lines 38-53 in `app.test.js`) are pre-written but skipped, un-skip them and run the tests before, during and after you write your code. + +### Result + +Your `app.js` file will now contain a button that decrements the counter and all the tests will run and pass. diff --git a/dom-merge-conflict/tasks/buttons-and-counter/src/app.js b/dom-merge-conflict/buttons-and-counter/src/app.js similarity index 100% rename from dom-merge-conflict/tasks/buttons-and-counter/src/app.js rename to dom-merge-conflict/buttons-and-counter/src/app.js diff --git a/dom-merge-conflict/tasks/buttons-and-counter/test/app.test.js b/dom-merge-conflict/buttons-and-counter/test/app.test.js similarity index 100% rename from dom-merge-conflict/tasks/buttons-and-counter/test/app.test.js rename to dom-merge-conflict/buttons-and-counter/test/app.test.js diff --git a/dom-merge-conflict/tasks/buttons-and-counter/instructions.md b/dom-merge-conflict/tasks/buttons-and-counter/instructions.md deleted file mode 100644 index b06f35f..0000000 --- a/dom-merge-conflict/tasks/buttons-and-counter/instructions.md +++ /dev/null @@ -1,23 +0,0 @@ -# Buttons and Counter - -This app currently features a button that increments a counter. - -## Task for Person 1 - -Create and switch to a new branch named `split-app-components`: - -``` -git switch -c split-app-components -``` - -Split the header, main into separate components. You should end up with an `app.js` file, a `header.js` file, and a `main.js` file in your `src` directory. - -## Task for Person 2 - -Create and switch to a new branch named `add-decrement-button`: - -``` -git switch -c add-decrement-button -``` - -Unskip the tests that describe the decrement button, then in the app add the new decrement button. Make sure to update the description of the app in the header. diff --git a/dom-merge-conflict/tasks/conditional-rendering/instructions.md b/dom-merge-conflict/tasks/conditional-rendering/instructions.md deleted file mode 100644 index 273feb1..0000000 --- a/dom-merge-conflict/tasks/conditional-rendering/instructions.md +++ /dev/null @@ -1,32 +0,0 @@ -# Conditional Rendering - -This component is currently a profile preview showing a person's image and name. -The argument profile is an object of form: - -``` -{ - pictureSrc : String, - name : String, - bio : String -} -``` - -## Task for Person 1 - -Create and switch to a new branch named `preview-not-available`: - -``` -git switch -c preview-not-available -``` - -Unskip the tests describing the available option. Update the component so that it takes an optional options argument which is an object that contains the available property. Check the tests to see how the `options` object is passed to the `ProfilePreview` component. When this property is false, the preview should not render any information other than a text saying "Profile preview unavailable". When this property is true, continue to render the profile as before. By default, available should be true. - -## Task for Person 2 - -Create and switch to a new branch named `preview-short-form`: - -``` -git switch -c preview-short-form -``` - -Unskip the tests describing the short form option. Update the component so that it takes an optional options argument which is an object that contains the shortForm property. Check the tests to see how the `options` object is passed to the `ProfilePreview` component. When this property is false, continue to render all the profile information. When this property is true, render all the profile information other than the bio. By default shortForm should be false. diff --git a/dom-merge-conflict/tasks/conditional-rendering/src/profilePreview.js b/dom-merge-conflict/tasks/conditional-rendering/src/profilePreview.js deleted file mode 100644 index b1778b4..0000000 --- a/dom-merge-conflict/tasks/conditional-rendering/src/profilePreview.js +++ /dev/null @@ -1,22 +0,0 @@ -export function ProfilePreview(profile) { - const preview = document.createElement("aside"); - - const picture = document.createElement("img"); - picture.src = profile.pictureSrc; - picture.alt = ""; - picture.dataset.testid = "profilePicture"; - - const name = document.createElement("p"); - name.textContent = profile.name; - name.dataset.testid = "profileName"; - - const bio = document.createElement("p"); - bio.textContent = profile.bio; - bio.dataset.testid = "profileBio"; - - preview.appendChild(picture); - preview.appendChild(name); - preview.appendChild(bio); - - return preview; -} diff --git a/dom-merge-conflict/tasks/conditional-rendering/test/profilePreview.test.js b/dom-merge-conflict/tasks/conditional-rendering/test/profilePreview.test.js deleted file mode 100644 index 8e50cec..0000000 --- a/dom-merge-conflict/tasks/conditional-rendering/test/profilePreview.test.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @jest-environment jsdom - */ - -import { getByTestId } from "@testing-library/dom"; -import "@testing-library/jest-dom"; - -import { ProfilePreview } from "../src/profilePreview"; - -describe("profile preview", () => { - describe("default options", () => { - test("contains profile info", () => { - const profile = { - pictureSrc: "/test", - name: "John Doe", - bio: "Lorem ipsum dolor sit amet.", - }; - //Freeze the object so that we can reference the properties in test and assume they are the same as above. - Object.freeze(profile); - - const container = ProfilePreview(profile); - - const image = getByTestId(container, "profilePicture"); - const name = getByTestId(container, "profileName"); - const bio = getByTestId(container, "profileBio"); - - expect(image.src).toContain(profile.pictureSrc); - expect(name).toHaveTextContent(profile.name); - expect(bio).toHaveTextContent(profile.bio); - expect(container.childElementCount).toBe(3); - }); - }); - - describe.skip("available option", () => { - test("only notice that preview is unavailable is displayed with shortform on, available off", () => { - const profile = { - pictureSrc: "/null", - name: "N/A", - bio: "N/A", - }; - Object.freeze(profile); - - const container = ProfilePreview(profile, { - available: false, - shortForm: true, - }); - - expect(container.childElementCount).toBe(1); - expect(container.children[0]).toHaveTextContent( - "Profile preview unavailable" - ); - }); - - test("only notice that preview is unavailable is displayed with shortform off, available off", () => { - const profile = { - pictureSrc: "/null", - name: "N/A", - bio: "N/A", - }; - Object.freeze(profile); - - const container = ProfilePreview(profile, { - available: false, - shortForm: false, - }); - - expect(container.childElementCount).toBe(1); - expect(container.children[0]).toHaveTextContent( - "Profile preview unavailable" - ); - }); - }); - - describe.skip("short form option", () => { - test("contains profile info except the bio with shortform on", () => { - const profile = { - pictureSrc: "/picture", - name: "Jane Doe", - bio: "Duis porta neque sed eros.", - }; - Object.freeze(profile); - - const container = ProfilePreview(profile, { - shortForm: true, - }); - - const image = getByTestId(container, "profilePicture"); - const name = getByTestId(container, "profileName"); - - expect(image.src).toContain(profile.pictureSrc); - expect(name).toHaveTextContent(profile.name); - expect(container.childElementCount).toBe(2); - }); - }); -}); diff --git a/dom-merge-conflict/tasks/optimistic-rendering/instructions.md b/dom-merge-conflict/tasks/optimistic-rendering/instructions.md deleted file mode 100644 index 747c940..0000000 --- a/dom-merge-conflict/tasks/optimistic-rendering/instructions.md +++ /dev/null @@ -1,23 +0,0 @@ -# Optimistic rendering - -This component is currently a like button, that updates once the promise from updateVote() is resolved and gives an error when it is rejected. - -## Task for Person 1 - -Create and switch to a new branch named `render-like-optimistically`: - -``` -git switch -c render-like-optimistically -``` - -Render the like optimistically, that is change the component to a liked state before the promise is resolved. If the promise rejects, then render the component back to its old state. Unskip the tests that describe optimistic rendering to test your implementation. - -## Task for Person 2 - -Create and switch to a new branch named `change-button-appearance`: - -``` -git switch -c change-button-appearance -``` - -Change the button content to an image that has a source of "/media/like". When liked, the component should render the image that has a source of "/media/liked" Be sure to add appropriate alt text. Unskip the tests that describe the like as a image to test your implementation. diff --git a/dom-merge-conflict/tasks/optimistic-rendering/src/voter.js b/dom-merge-conflict/tasks/optimistic-rendering/src/voter.js deleted file mode 100644 index 0186989..0000000 --- a/dom-merge-conflict/tasks/optimistic-rendering/src/voter.js +++ /dev/null @@ -1,30 +0,0 @@ -export function Voter(updateVote) { - const container = document.createElement("div"); - - const button = document.createElement("button"); - button.textContent = "Like"; - - const errorMessage = document.createElement("p"); - - container.appendChild(button); - - button.addEventListener("click", () => { - errorMessage.remove(); - - updateVote() - .then(() => { - const message = document.createElement("p"); - message.textContent = "Liked"; - - container.appendChild(message); - button.remove(); - }) - .catch(() => { - errorMessage.textContent = - "We could not process your vote, please try again later."; - container.appendChild(errorMessage); - }); - }); - - return container; -} diff --git a/dom-merge-conflict/tasks/optimistic-rendering/test/voter.test.js b/dom-merge-conflict/tasks/optimistic-rendering/test/voter.test.js deleted file mode 100644 index 5e310fd..0000000 --- a/dom-merge-conflict/tasks/optimistic-rendering/test/voter.test.js +++ /dev/null @@ -1,195 +0,0 @@ -/** - * @jest-environment jsdom - */ - -import { - queryByAltText, - getByRole, - queryByRole, - queryByText, - queryAllByText, -} from "@testing-library/dom"; -import "@testing-library/jest-dom"; - -import { Voter } from "../src/voter"; - -describe("like button", () => { - describe("intial state", () => { - const pending = new Promise(() => {}); - - const container = Voter(() => pending); - - test("starts with like button", () => { - const button = queryByRole(container, "button", { - name: /like/i, - }); - - expect(button).not.toBeNull(); - }); - - test("starts with 1 child", () => { - expect(container.childElementCount).toBe(1); - }); - }); - - describe("resolved state", () => { - let resolve = null; - let container = null; - - beforeEach(() => { - resolve = Promise.resolve(); - container = Voter(() => resolve); - - const button = getByRole(container, "button"); - button.click(); - }); - - test("ends with 1 child", () => { - expect.assertions(1); - - return resolve.then(() => { - expect(container.childElementCount).toBe(1); - }); - }); - - test("ends with no button", async () => { - expect.assertions(1); - - return resolve.then(() => { - expect(queryByRole(container, "button")).toBeNull(); - }); - }); - }); - - describe("rejected state", () => { - let reject = null; - let container = null; - - beforeEach(() => { - reject = Promise.reject(); - container = Voter(() => reject); - - const button = queryByRole(container, "button"); - button.click(); - }); - - test("displays error message", () => { - expect.assertions(1); - - return reject.catch(() => { - const errorMessage = queryByText( - container, - /we could not process your vote/i - ); - - expect(errorMessage).not.toBeNull(); - }); - }); - - test("has two children after fail", () => { - expect.assertions(1); - - return reject.catch(() => { - expect(container.childElementCount).toBe(2); - }); - }); - - test("has button after fail", () => { - expect.assertions(1); - - return reject.catch(() => { - const button = queryByRole(container, "button"); - - expect(button).not.toBeNull(); - }); - }); - - test("pressing button after fail instantly removes error message", () => { - expect.assertions(2); - - return reject.catch(() => { - const button = queryByRole(container, "button"); - - button.click(); - - const errorMessage = queryByText( - container, - /we could not process your vote/i - ); - - expect(errorMessage).toBeNull(); - expect(container.childElementCount).toBe(1); - }); - }); - }); - - describe.skip("optimistic rendering", () => { - let pending = null; - let container = null; - - beforeEach(() => { - pending = new Promise(() => {}); - container = Voter(() => pending); - - const button = queryByRole(container, "button"); - button.click(); - }); - - test("there is 1 child immediately after click", () => { - expect(container.childElementCount).toBe(1); - }); - - test("there is no button immediately after click", () => { - const button = queryByRole(container, "button"); - - expect(button).toBeNull(); - }); - }); - - describe.skip("button as an image", () => { - let resolve = null; - let container = null; - - beforeEach(() => { - resolve = Promise.resolve(); - container = Voter(() => resolve); - }); - - test("component contains no text content", () => { - const button = queryByRole(container, "button"); - button.click(); - - const text = queryAllByText(container, /(\s|\S)/i); - - expect(text).toHaveLength(0); - }); - - test("contains no empty paragraphs components", () => { - const button = queryByRole(container, "button"); - button.click(); - - const paragraphs = container.querySelectorAll("p"); - - expect(paragraphs).toHaveLength(0); - }); - - test("button icon uses /media/like as src", () => { - const icon = queryByAltText(container, /like/i); - - expect(icon.src).toMatch(/media\/like$/i); - }); - - test("once resolved icon uses /media/liked as src", () => { - expect.assertions(1); - - const button = queryByRole(container, "button"); - button.click(); - - return resolve.then(() => { - const icon = queryByAltText(container, /liked/i); - - expect(icon.src).toMatch(/media\/liked$/i); - }); - }); - }); -}); diff --git a/dom-merge-conflict/tasks/processing-page/instructions.md b/dom-merge-conflict/tasks/processing-page/instructions.md deleted file mode 100644 index 28e08bf..0000000 --- a/dom-merge-conflict/tasks/processing-page/instructions.md +++ /dev/null @@ -1,36 +0,0 @@ -# Processing Page - -This component currently features a processing page which changes after the completion of some promises. -Below you can see how the functions that return promises depend on each other. "→" means that the result of the previous promise is passed into the next function: - -``` -verifyServersideKey → verifyLocalKey -``` - -``` -verifyBrowser → verifyVersion -``` - -``` -verifyStableConnection -``` - -## Task for Person 1 - -Create and switch to a new branch named `add-progress-bar`: - -``` -git switch -c add-progress-bar -``` - -Unskip the tests describing the progress bar. Then implement a progress bar underneath the current message that increments its value for each promise resolved. - -## Task for Person 2 - -Create and switch to a new branch named `change-promise-logic`: - -``` -git switch -c change-promise-logic -``` - -Unskip the tests describing promises running concurrently. Update the component so that the operations that are independent of each other run concurrently. diff --git a/dom-merge-conflict/tasks/processing-page/src/processingPage.js b/dom-merge-conflict/tasks/processing-page/src/processingPage.js deleted file mode 100644 index 3ce8e76..0000000 --- a/dom-merge-conflict/tasks/processing-page/src/processingPage.js +++ /dev/null @@ -1,29 +0,0 @@ -export function ProcessingPage({ - verifyServersideKey, - verifyLocalKey, - verifyBrowser, - verifyVersion, - verifyStableConnection, -}) { - const container = document.createElement("div"); - - const info = document.createElement("p"); - info.textContent = - "Please wait while we verify your details, browser, and internet connection."; - info.dataset.testid = "info"; - container.appendChild(info); - - verifyServersideKey() - .then((res) => verifyLocalKey(res)) - .then(() => verifyBrowser()) - .then((res) => verifyVersion(res)) - .then(() => verifyStableConnection()) - .then(() => { - info.textContent = "Verification successful."; - }) - .catch(() => { - info.textContent = "Verification failed."; - }); - - return container; -} diff --git a/dom-merge-conflict/tasks/processing-page/test/processingPage.test.js b/dom-merge-conflict/tasks/processing-page/test/processingPage.test.js deleted file mode 100644 index ac090d1..0000000 --- a/dom-merge-conflict/tasks/processing-page/test/processingPage.test.js +++ /dev/null @@ -1,235 +0,0 @@ -/** - * @jest-environment jsdom - */ - -import "@testing-library/jest-dom"; - -import { ProcessingPage } from "../src/processingPage"; -import { getByRole, getByTestId } from "@testing-library/dom"; - -//waitForPromises is used to wait for next event loop in node.js which allows for all promises to be handled in the component being tested. -function waitForPromises() { - return new Promise(process.nextTick); -} - -describe("processing page", () => { - // The MockHelper will be an object that contain {promise, callback, resolve, reject}. callback is required by LoadingPage - let verifyServersideKeyMockHelper = null; - let verifyLocalKeyMockHelper = null; - let verifyBrowserMockHelper = null; - let verifyVersionMockHelper = null; - let verifyStableConnectionMockHelper = null; - let container = null; - - function generateMockHelper() { - let resolve, reject; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - const callback = () => promise; - return { resolve, reject, promise, callback }; - } - - beforeEach(() => { - verifyServersideKeyMockHelper = generateMockHelper(); - verifyLocalKeyMockHelper = generateMockHelper(); - verifyBrowserMockHelper = generateMockHelper(); - verifyVersionMockHelper = generateMockHelper(); - verifyStableConnectionMockHelper = generateMockHelper(); - - container = ProcessingPage({ - verifyServersideKey: verifyServersideKeyMockHelper.callback, - verifyLocalKey: verifyLocalKeyMockHelper.callback, - verifyBrowser: verifyBrowserMockHelper.callback, - verifyVersion: verifyVersionMockHelper.callback, - verifyStableConnection: verifyStableConnectionMockHelper.callback, - }); - }); - - describe("default", () => { - test("the info text initially displays a message to wait", () => { - expect(getByTestId(container, "info")).toHaveTextContent(/^please wait/i); - }); - - test("the info text does not change if the verifyLocalKey promise resolves by itself", () => { - expect.assertions(1); - - verifyLocalKeyMockHelper.resolve(); - - return waitForPromises().then(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^please wait/i - ); - }); - }); - - test("the info text does not change if the verifyLocalKey promise rejects by itself", () => { - expect.assertions(1); - - verifyLocalKeyMockHelper.reject(); - - return waitForPromises() - .then(() => verifyLocalKeyMockHelper.promise) - .catch(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^please wait/i - ); - }); - }); - - test("the info text does not change if the verifyVersion promise resolves by itself", () => { - expect.assertions(1); - - verifyVersionMockHelper.resolve(); - - return waitForPromises().then(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^please wait/i - ); - }); - }); - - test("the info text does not change if the verifyVersion promise rejects by itself", () => { - expect.assertions(1); - - verifyVersionMockHelper.reject(); - - return waitForPromises() - .then(() => verifyVersionMockHelper.promise) - .catch(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^please wait/i - ); - }); - }); - - test("the info text indicates success in verification when all promises are resolved", () => { - expect.assertions(1); - - verifyServersideKeyMockHelper.resolve(); - verifyLocalKeyMockHelper.resolve(); - verifyBrowserMockHelper.resolve(); - verifyVersionMockHelper.resolve(); - verifyStableConnectionMockHelper.resolve(); - - return waitForPromises().then(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^verification successful/i - ); - }); - }); - }); - - describe.skip("progress bar", () => { - test("progress bar is initially at 0 and has a max of 5", () => { - const progressbar = getByRole(container, "progressbar"); - - expect(progressbar.value).toBe(0); - expect(progressbar.max).toBe(5); - }); - - test("progress bar increments for each completed task", () => { - expect.assertions(1); - - verifyServersideKeyMockHelper.resolve(); - verifyLocalKeyMockHelper.resolve(); - verifyBrowserMockHelper.resolve(); - verifyVersionMockHelper.resolve(); - - return waitForPromises().then(() => { - const progressbar = getByRole(container, "progressbar"); - - expect(progressbar.value).toBe(4); - }); - }); - - test("progress bar does not increment when promises that are dependent on other promises resolve", () => { - expect.assertions(1); - - verifyServersideKeyMockHelper.resolve(); - verifyLocalKeyMockHelper.resolve(); - verifyVersionMockHelper.resolve(); - - return waitForPromises().then(() => { - const progressbar = getByRole(container, "progressbar"); - - expect(progressbar.value).toBe(2); - }); - }); - }); - - describe.skip("run independent promises concurrently", () => { - test("info text indicates failure when verifyStableConnection rejects", () => { - expect.assertions(1); - - verifyStableConnectionMockHelper.reject(); - - return waitForPromises() - .then(() => verifyStableConnectionMockHelper.promise) - .catch(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^verification failed/i - ); - }); - }); - - test("info text indicates failure when verifyServersideKey rejects", () => { - expect.assertions(1); - - verifyServersideKeyMockHelper.reject(); - - return waitForPromises() - .then(() => verifyServersideKeyMockHelper.promise) - .catch(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^verification failed/i - ); - }); - }); - - test("info text indicates failure when verifyBrowser rejects", () => { - expect.assertions(1); - - verifyBrowserMockHelper.reject(); - - return waitForPromises() - .then(() => verifyBrowserMockHelper.promise) - .catch(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^verification failed/i - ); - }); - }); - - test("info text indicates failure when verifyLocalKey rejects after verifyServerSideKey resolves", () => { - expect.assertions(1); - - verifyServersideKeyMockHelper.resolve(); - verifyLocalKeyMockHelper.reject(); - - return waitForPromises() - .then(() => verifyLocalKeyMockHelper.promise) - .catch(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^verification failed/i - ); - }); - }); - - test("info text indicates faiulre when verifyVersion reject after verifyBrowser resolves", () => { - expect.assertions(1); - - verifyBrowserMockHelper.resolve(); - verifyVersionMockHelper.reject(); - - return waitForPromises() - .then(() => verifyVersionMockHelper.promise) - .catch(() => { - expect(getByTestId(container, "info")).toHaveTextContent( - /^verification failed/i - ); - }); - }); - }); -});