diff --git a/Sprint-3/slideshow/.gitignore b/Sprint-3/slideshow/.gitignore new file mode 100644 index 000000000..c3365d715 --- /dev/null +++ b/Sprint-3/slideshow/.gitignore @@ -0,0 +1,8 @@ +*.pdf +*.docx +prep/ +package-lock.json +../package-lock.json +../../Sprint-1/package-lock.json +../../Sprint-2/package-lock.json +../alarmclock/package.json diff --git a/Sprint-3/slideshow/images/puppy1.jpg b/Sprint-3/slideshow/images/puppy1.jpg new file mode 100644 index 000000000..6fc7f4d69 Binary files /dev/null and b/Sprint-3/slideshow/images/puppy1.jpg differ diff --git a/Sprint-3/slideshow/images/puppy2.jpg b/Sprint-3/slideshow/images/puppy2.jpg new file mode 100644 index 000000000..456804a97 Binary files /dev/null and b/Sprint-3/slideshow/images/puppy2.jpg differ diff --git a/Sprint-3/slideshow/images/puppy3.jpg b/Sprint-3/slideshow/images/puppy3.jpg new file mode 100644 index 000000000..441f8a438 Binary files /dev/null and b/Sprint-3/slideshow/images/puppy3.jpg differ diff --git a/Sprint-3/slideshow/images/puppy4.jpg b/Sprint-3/slideshow/images/puppy4.jpg new file mode 100644 index 000000000..b820c723d Binary files /dev/null and b/Sprint-3/slideshow/images/puppy4.jpg differ diff --git a/Sprint-3/slideshow/index.html b/Sprint-3/slideshow/index.html index 50f2eb1c0..653a08278 100644 --- a/Sprint-3/slideshow/index.html +++ b/Sprint-3/slideshow/index.html @@ -1,14 +1,30 @@ - - - Title here + + + Image carousel + - cat-pic - - + puppy-pic + +
+

Slideshow Controls

+ + +
+ + +
+ + +
+ + + +
+
diff --git a/Sprint-3/slideshow/slideshow.js b/Sprint-3/slideshow/slideshow.js index 063ceefb5..319fd49cd 100644 --- a/Sprint-3/slideshow/slideshow.js +++ b/Sprint-3/slideshow/slideshow.js @@ -1,8 +1,66 @@ const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; +let currentIndex = 0; +let intervalId = null; +const intervalTime = 2000; // 2 seconds -// Write your code here \ No newline at end of file +// Get DOM elements +const carouselImg = document.getElementById("carousel-img"); +const forwardBtn = document.getElementById("forward-btn"); +const backwardBtn = document.getElementById("backward-btn"); +const autoForwardBtn = document.getElementById("auto-forward"); +const autoBackwardBtn = document.getElementById("auto-backward"); +const stopBtn = document.getElementById("stop"); + +// Show image helper +function showImage(index) { + carouselImg.src = images[index]; +} + +// Stop auto-advance +function stopAuto() { + clearInterval(intervalId); + intervalId = null; + autoForwardBtn.disabled = false; + autoBackwardBtn.disabled = false; +} + +// Manual navigation +function nextImage() { + if (intervalId) stopAuto(); // stop auto if running + currentIndex = (currentIndex + 1) % images.length; + showImage(currentIndex); +} + +function prevImage() { + if (intervalId) stopAuto(); // stop auto if running + currentIndex = (currentIndex - 1 + images.length) % images.length; + showImage(currentIndex); +} + +forwardBtn.addEventListener("click", nextImage); +backwardBtn.addEventListener("click", prevImage); + +// Auto navigation +function startAuto(directionFn) { + autoForwardBtn.disabled = true; + autoBackwardBtn.disabled = true; + intervalId = setInterval(directionFn, intervalTime); +} + +autoForwardBtn.addEventListener("click", () => startAuto(() => { + currentIndex = (currentIndex + 1) % images.length; + showImage(currentIndex); +})); + +autoBackwardBtn.addEventListener("click", () => startAuto(() => { + currentIndex = (currentIndex - 1 + images.length) % images.length; + showImage(currentIndex); +})); + +stopBtn.addEventListener("click", stopAuto); \ No newline at end of file diff --git a/Sprint-3/slideshow/slideshow.test.js b/Sprint-3/slideshow/slideshow.test.js index 34f802a3e..fa1b190e2 100644 --- a/Sprint-3/slideshow/slideshow.test.js +++ b/Sprint-3/slideshow/slideshow.test.js @@ -14,7 +14,6 @@ beforeEach(async () => { runScripts: "dangerously", }); - // do this so students can use element.innerText which jsdom does not implement Object.defineProperty(page.window.HTMLElement.prototype, "innerText", { get() { return this.textContent; @@ -36,9 +35,10 @@ afterEach(() => { describe("Level 1 challenge", () => { test("renders the first image with control buttons", () => { const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; const image = page.window.document.querySelector("#carousel-img"); const forwardBtn = page.window.document.querySelector("#forward-btn"); @@ -48,11 +48,13 @@ describe("Level 1 challenge", () => { expect(forwardBtn).toBeInTheDocument(); expect(backwardBtn).toBeInTheDocument(); }); + test("can move the image forwards once", () => { const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; const image = page.window.document.querySelector("#carousel-img"); const forwardBtn = page.window.document.querySelector("#forward-btn"); @@ -66,9 +68,10 @@ describe("Level 1 challenge", () => { test("can move the image forwards multiple times", () => { const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; const image = page.window.document.querySelector("#carousel-img"); const forwardBtn = page.window.document.querySelector("#forward-btn"); @@ -79,11 +82,49 @@ describe("Level 1 challenge", () => { expect(image).toHaveAttribute("src", images[2]); }); - test("can move the image backwards to the end", () => { + test("can move the image forwards to the last image", () => { + const images = [ + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", + ]; + const image = page.window.document.querySelector("#carousel-img"); + const forwardBtn = page.window.document.querySelector("#forward-btn"); + + userEvent.click(forwardBtn); + userEvent.click(forwardBtn); + userEvent.click(forwardBtn); + + expect(image).toHaveAttribute("src", images[3]); + }); + + test("moving forwards will eventually wrap around to the start", () => { + const images = [ + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", + ]; + const image = page.window.document.querySelector("#carousel-img"); + const forwardBtn = page.window.document.querySelector("#forward-btn"); + + expect(image).toHaveAttribute("src", images[0]); + + userEvent.click(forwardBtn); + userEvent.click(forwardBtn); + userEvent.click(forwardBtn); + userEvent.click(forwardBtn); + + expect(image).toHaveAttribute("src", images[0]); + }); + + test("can move the image backwards to the last image", () => { const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; const image = page.window.document.querySelector("#carousel-img"); const backwardBtn = page.window.document.querySelector("#backward-btn"); @@ -92,39 +133,41 @@ describe("Level 1 challenge", () => { userEvent.click(backwardBtn); - expect(image).toHaveAttribute("src", images[2]); + expect(image).toHaveAttribute("src", images[3]); }); test("can move the image backwards multiple times", () => { const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; const image = page.window.document.querySelector("#carousel-img"); const backwardBtn = page.window.document.querySelector("#backward-btn"); - expect(image).toHaveAttribute("src", images[0]); userEvent.click(backwardBtn); userEvent.click(backwardBtn); - expect(image).toHaveAttribute("src", images[1]); + expect(image).toHaveAttribute("src", images[2]); }); - test("moving forwards will eventually wrap around to the start", () => { + test("moving backwards will eventually wrap around to the start", () => { const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; const image = page.window.document.querySelector("#carousel-img"); - const forwardBtn = page.window.document.querySelector("#forward-btn"); + const backwardBtn = page.window.document.querySelector("#backward-btn"); expect(image).toHaveAttribute("src", images[0]); - userEvent.click(forwardBtn); - userEvent.click(forwardBtn); - userEvent.click(forwardBtn); + userEvent.click(backwardBtn); + userEvent.click(backwardBtn); + userEvent.click(backwardBtn); + userEvent.click(backwardBtn); expect(image).toHaveAttribute("src", images[0]); }); @@ -137,11 +180,13 @@ describe("Level 2 challenge", () => { afterEach(() => { jest.useRealTimers(); }); + test("can start moving images forward automatically", () => { const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; const image = page.window.document.querySelector("#carousel-img"); const autoForwardBtn = page.window.document.querySelector("#auto-forward"); @@ -161,14 +206,19 @@ describe("Level 2 challenge", () => { jest.advanceTimersByTime(interval); expect(image).toHaveAttribute("src", images[2]); + jest.advanceTimersByTime(interval); + expect(image).toHaveAttribute("src", images[3]); + jest.advanceTimersByTime(interval); expect(image).toHaveAttribute("src", images[0]); }); + test("can start moving images backward automatically", () => { const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; const image = page.window.document.querySelector("#carousel-img"); const autoForwardBtn = page.window.document.querySelector("#auto-forward"); @@ -182,6 +232,9 @@ describe("Level 2 challenge", () => { expect(autoForwardBtn).toBeDisabled(); expect(autoBackBtn).toBeDisabled(); + jest.advanceTimersByTime(interval); + expect(image).toHaveAttribute("src", images[3]); + jest.advanceTimersByTime(interval); expect(image).toHaveAttribute("src", images[2]); @@ -191,11 +244,13 @@ describe("Level 2 challenge", () => { jest.advanceTimersByTime(interval); expect(image).toHaveAttribute("src", images[0]); }); + test("can stop the automatic timer", () => { const images = [ - "./assets/cute-cat-a.png", - "./assets/cute-cat-b.jpg", - "./assets/cute-cat-c.jpg", + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", ]; const image = page.window.document.querySelector("#carousel-img"); const autoForwardBtn = page.window.document.querySelector("#auto-forward"); @@ -224,4 +279,50 @@ describe("Level 2 challenge", () => { jest.runOnlyPendingTimers(); expect(image).toHaveAttribute("src", images[2]); }); -}); + + test("clicking forward or backward while auto-advancing stops auto mode", () => { + const images = [ + "./images/puppy1.jpg", + "./images/puppy2.jpg", + "./images/puppy3.jpg", + "./images/puppy4.jpg", + ]; + const image = page.window.document.querySelector("#carousel-img"); + const autoForwardBtn = page.window.document.querySelector("#auto-forward"); + const autoBackBtn = page.window.document.querySelector("#auto-backward"); + const forwardBtn = page.window.document.querySelector("#forward-btn"); + const backwardBtn = page.window.document.querySelector("#backward-btn"); + const interval = 2000; + + // Start auto-forward + userEvent.click(autoForwardBtn); + expect(autoForwardBtn).toBeDisabled(); + expect(autoBackBtn).toBeDisabled(); + + // It advances once + jest.advanceTimersByTime(interval); + expect(image).toHaveAttribute("src", images[1]); + + // Manual forward should STOP auto + userEvent.click(forwardBtn); + expect(autoForwardBtn).toBeEnabled(); + expect(autoBackBtn).toBeEnabled(); + + // Time passes, but image no longer auto-advances + jest.advanceTimersByTime(interval * 2); + expect(image).toHaveAttribute("src", images[2]); + + // Start auto-backward, then stop via manual backward + userEvent.click(autoBackBtn); + jest.advanceTimersByTime(interval); + expect(image).toHaveAttribute("src", images[1]); + + userEvent.click(backwardBtn); // stops auto again + jest.advanceTimersByTime(interval); + expect(image).toHaveAttribute("src", images[0]); + + // Cleanup pending timers in this test + jest.runOnlyPendingTimers(); + }); + +}); \ No newline at end of file diff --git a/Sprint-3/slideshow/style.css b/Sprint-3/slideshow/style.css index 63cedf2d2..94427ce1a 100644 --- a/Sprint-3/slideshow/style.css +++ b/Sprint-3/slideshow/style.css @@ -1 +1,47 @@ -/** Write your CSS in here **/ +body { + font-family: Arial, sans-serif; + background-color: #f4f4f4; + margin: 0; + padding: 20px; + text-align: center; +} + +h1 { + margin-bottom: 20px; +} + +#carousel-img { + width: 400px; + max-width: 100%; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); + margin-bottom: 15px; +} + +/* Updated: use .controls instead of .button-row to match HTML */ +.controls { + display: flex; + justify-content: center; + gap: 10px; /* space between buttons in the same row */ + margin-bottom: 20px; /* space between rows (manual vs auto) */ +} + +button { + background-color: #0077cc; + color: white; + border: none; + padding: 10px 15px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + min-width: 120px; +} + +button:hover:not(:disabled) { + background-color: #005fa3; +} + +button:disabled { + background-color: #cccccc; + cursor: not-allowed; +}