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
+
-
-
-
+
+
+
+ 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;
+}