This commit is contained in:
@@ -5,7 +5,7 @@ const ListUser = (props) => {
|
|||||||
// const { } = props;
|
// const { } = props;
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
// const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const controller = new AbortController(); // 2019
|
const controller = new AbortController(); // 2019
|
||||||
@@ -18,7 +18,8 @@ const ListUser = (props) => {
|
|||||||
})
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
setUsers(data?.users);
|
setUsers(data?.users ?? []);
|
||||||
|
setError(null);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
//
|
//
|
||||||
@@ -27,6 +28,7 @@ const ListUser = (props) => {
|
|||||||
console.error('Request abgebrochen:', err.message);
|
console.error('Request abgebrochen:', err.message);
|
||||||
} else {
|
} else {
|
||||||
console.error('Error:', err);
|
console.error('Error:', err);
|
||||||
|
setError(err);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -47,6 +49,15 @@ const ListUser = (props) => {
|
|||||||
return <p className="text-muted alert alert-secondary">is loading users...</p>;
|
return <p className="text-muted alert alert-secondary">is loading users...</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
const status = error.response?.status;
|
||||||
|
return (
|
||||||
|
<p className="alert alert-danger" role="alert">
|
||||||
|
{status ? `Failed to load users (HTTP ${status})` : 'Failed to load users'}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="list-todo list-group">
|
<ul className="list-todo list-group">
|
||||||
{users.length > 0 &&
|
{users.length > 0 &&
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import { describe, it, afterEach } from 'vitest';
|
|
||||||
|
|
||||||
import axios from 'axios';
|
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
|
||||||
import ListUser from '../components/lists/ListUser';
|
|
||||||
|
|
||||||
// Create a new instance of the MockAdapter
|
|
||||||
const mock = new MockAdapter(axios);
|
|
||||||
|
|
||||||
// Clean up after each test case to prevent interference between tests
|
|
||||||
afterEach(() => {
|
|
||||||
mock.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ListUser', () => {
|
|
||||||
it('should load and display user data', async () => {
|
|
||||||
render(<ListUser />);
|
|
||||||
const mockData = { users: [{ id: 1, firstName: 'John' }] };
|
|
||||||
// Configure the mock: When this specific URL is requested, return 200 and our mockData
|
|
||||||
// This intercepts the network call, so no real internet request happens
|
|
||||||
mock.onGet('https://dummyjson.com/users?limit=3').reply(200, mockData);
|
|
||||||
|
|
||||||
const userElement = await screen.findByText(/John/i);
|
|
||||||
|
|
||||||
expect(userElement).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { describe, it, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
import ListUser from '../components/lists/ListUser';
|
||||||
|
|
||||||
|
const USERS_URL = 'https://dummyjson.com/users?limit=100';
|
||||||
|
|
||||||
|
const mockUsersResponse = {
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
age: 28,
|
||||||
|
email: 'john.doe@example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: 1,
|
||||||
|
skip: 0,
|
||||||
|
limit: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mock = new MockAdapter(axios);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mock.reset();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ListUser', () => {
|
||||||
|
it('should load and display user data on HTTP 200', async () => {
|
||||||
|
mock.onGet(USERS_URL).reply(200, mockUsersResponse);
|
||||||
|
|
||||||
|
render(<ListUser />);
|
||||||
|
|
||||||
|
expect(screen.getByText(/is loading users/i)).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(await screen.findByText(/John/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/Doe/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/28/)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/john.doe@example.com/i)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/is loading users/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show an empty list on HTTP 200 when no users are returned', async () => {
|
||||||
|
mock.onGet(USERS_URL).reply(200, { users: [], total: 0, skip: 0, limit: 100 });
|
||||||
|
|
||||||
|
render(<ListUser />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(/is loading users/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[400, 'Bad Request'],
|
||||||
|
[401, 'Unauthorized'],
|
||||||
|
[403, 'Forbidden'],
|
||||||
|
[404, 'Not Found'],
|
||||||
|
[500, 'Internal Server Error'],
|
||||||
|
[503, 'Service Unavailable'],
|
||||||
|
])('should show an error for HTTP %s (%s)', async (status) => {
|
||||||
|
mock.onGet(USERS_URL).reply(status);
|
||||||
|
|
||||||
|
render(<ListUser />);
|
||||||
|
|
||||||
|
expect(await screen.findByRole('alert')).toHaveTextContent(`Failed to load users (HTTP ${status})`);
|
||||||
|
expect(screen.queryByText(/John/i)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/is loading users/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show a generic error on network failure', async () => {
|
||||||
|
mock.onGet(USERS_URL).networkError();
|
||||||
|
|
||||||
|
render(<ListUser />);
|
||||||
|
|
||||||
|
expect(await screen.findByRole('alert')).toHaveTextContent('Failed to load users');
|
||||||
|
expect(screen.queryByText(/HTTP/i)).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/John/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -30,7 +30,7 @@ describe('CategorySection', () => {
|
|||||||
const imgs = screen.getAllByRole('img');
|
const imgs = screen.getAllByRole('img');
|
||||||
const links = screen.getAllByRole('link');
|
const links = screen.getAllByRole('link');
|
||||||
|
|
||||||
expect(imgs).toHaveLength(3); //.length()
|
expect(imgs).toHaveLength(3); //.length() - from chai - TestFramework
|
||||||
expect(links).toHaveLength(3);
|
expect(links).toHaveLength(3); // .lengthOf()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe('Footer', () => {
|
|||||||
|
|
||||||
navItems.forEach((item) => {
|
navItems.forEach((item) => {
|
||||||
const spanEl = screen.getByText(item.name);
|
const spanEl = screen.getByText(item.name);
|
||||||
const linkEl = spanEl.parentElement;
|
const linkEl = spanEl.parentNode;
|
||||||
expect(spanEl).toBeInTheDocument();
|
expect(spanEl).toBeInTheDocument();
|
||||||
expect(linkEl).toHaveAttribute('href', item.href);
|
expect(linkEl).toHaveAttribute('href', item.href);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,11 +65,11 @@ describe('ImageGallery', () => {
|
|||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
expect(mainImage).toBeInTheDocument();
|
||||||
expect(mainImage).toHaveAttribute('src', product.images[0]);
|
expect(mainImage).toHaveAttribute('src', product.images[0]);
|
||||||
|
|
||||||
await user.keyboard('{ArrowRight}');
|
await user.keyboard('{ArrowRight}');
|
||||||
|
|
||||||
expect(mainImage).toBeInTheDocument();
|
|
||||||
expect(mainImage).toHaveAttribute('src', product.images[1]);
|
expect(mainImage).toHaveAttribute('src', product.images[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,6 +79,7 @@ describe('ImageGallery', () => {
|
|||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
expect(mainImage).toBeInTheDocument();
|
||||||
expect(mainImage).toHaveAttribute('src', product.images[0]);
|
expect(mainImage).toHaveAttribute('src', product.images[0]);
|
||||||
await user.keyboard('{ArrowRight}');
|
await user.keyboard('{ArrowRight}');
|
||||||
|
|
||||||
@@ -86,7 +87,6 @@ describe('ImageGallery', () => {
|
|||||||
|
|
||||||
await user.keyboard('{ArrowLeft}');
|
await user.keyboard('{ArrowLeft}');
|
||||||
|
|
||||||
expect(mainImage).toBeInTheDocument();
|
|
||||||
expect(mainImage).toHaveAttribute('src', product.images[0]);
|
expect(mainImage).toHaveAttribute('src', product.images[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,10 +96,11 @@ describe('ImageGallery', () => {
|
|||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
expect(mainImage).toBeInTheDocument();
|
||||||
expect(mainImage).toHaveAttribute('src', product.images[0]);
|
expect(mainImage).toHaveAttribute('src', product.images[0]);
|
||||||
|
|
||||||
await user.keyboard('{ArrowLeft}');
|
await user.keyboard('{ArrowLeft}');
|
||||||
|
|
||||||
expect(mainImage).toBeInTheDocument();
|
|
||||||
expect(mainImage).toHaveAttribute('src', product.images[product.images.length - 1]);
|
expect(mainImage).toHaveAttribute('src', product.images[product.images.length - 1]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const isSystemUpgrading = false;
|
||||||
|
const isLoggedIn = true;
|
||||||
|
const hasAdminRights = true;
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ name: 'Item A', cost: 150 },
|
||||||
|
{ name: 'Item B', cost: 250 },
|
||||||
|
{ name: 'Item C', cost: 350 },
|
||||||
|
];
|
||||||
|
|
||||||
|
function runNested() {
|
||||||
|
let counter = 0;
|
||||||
|
if (!isSystemUpgrading) {
|
||||||
|
if (isLoggedIn) {
|
||||||
|
if (hasAdminRights) {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].cost > 200) counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runGuard() {
|
||||||
|
let counter = 0;
|
||||||
|
if (isSystemUpgrading) return counter;
|
||||||
|
if (!isLoggedIn) return counter;
|
||||||
|
if (!hasAdminRights) return counter;
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].cost > 200) counter++;
|
||||||
|
}
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ITERATIONS = 10_000_000;
|
||||||
|
|
||||||
|
// Warmup
|
||||||
|
for (let i = 0; i < 100_000; i++) {
|
||||||
|
runNested();
|
||||||
|
runGuard();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.time('Verschachtelt');
|
||||||
|
for (let i = 0; i < ITERATIONS; i++) runNested();
|
||||||
|
console.timeEnd('Verschachtelt');
|
||||||
|
|
||||||
|
console.time('Early Returns');
|
||||||
|
for (let i = 0; i < ITERATIONS; i++) runGuard();
|
||||||
|
console.timeEnd('Early Returns');
|
||||||
|
|
||||||
|
// In diesem Test laufen beide Varianten im Chrome V8-Engine praktisch gleich schnell (innerhalb normaler CPU-Schwankungen).
|
||||||
|
|
||||||
|
// Fazit: Verwende Early Returns (Guard Clauses). Sie bieten zwar keinen nennenswerten Performance-Vorsprung gegenüber verschachtelten Abfragen, reduzieren aber die zyklomatische Komplexität und machen den Code deutlich lesbarer.
|
||||||
38
06_js-debug/unterricht/tag46/01_inversion/04_README.md
Normal file
38
06_js-debug/unterricht/tag46/01_inversion/04_README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
## Kein Performanceunterschied bei Inversion
|
||||||
|
|
||||||
|
### 1. AST-Normalisierung & SSA (Control Flow Graphs)
|
||||||
|
|
||||||
|
V8 (über den Turbofan-Optimierungs-Compiler) führt deinen Code nicht zeilenweise so aus, wie du ihn schreibst:
|
||||||
|
|
||||||
|
* **Control Flow Graph (CFG):** V8 wandelt sowohl verschachtelte Blöcke als auch Early Returns in dieselbe abstrakte Repräsentation um (Static Single Assignment / SSA Form).
|
||||||
|
* **Pfad-Reduktion:** Ein verschachtelter Baum `if (A) { if (B) { if (C) { ... } } }` wird im Zwischencode auf denselben Entscheidungspfad reduziert wie `if (!A) return; if (!B) return; if (!C) return;`.
|
||||||
|
* **Identischer Maschinencode:** Sobald Turbofan den Code optimiert, erzeugen beide Varianten exakt dieselbe Sequenz aus bedingten Sprungbefehlen (`test`, `jnz` / `jz`) auf Assemblerebene.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Hardware-Ebene: CPU Branch Prediction
|
||||||
|
|
||||||
|
Unabhängig von der JS-Engine entscheidet die Hardware über die Geschwindigkeit von Verzweigungen:
|
||||||
|
|
||||||
|
* Wenn Bedingungen wie `isLoggedIn` stabil sind (z. B. immer `true`), lernt der **Branch Predictor** der CPU das Muster nach wenigen Durchläufen.
|
||||||
|
* Die CPU führt den Zweig spekulativ ohne Pipeline-Stall aus (Branch Prediction Penalty = 0 Takte).
|
||||||
|
* Ob der nicht genommene Zweig am Ende der Funktion (`nested`) oder direkt hinter dem Check (`early return`) liegt, macht für die Ausführungszeit der CPU keinen Unterschied.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Warum misst man im Firefox (SpiderMonkey) manchmal Unterschiede?
|
||||||
|
|
||||||
|
SpiderMonkey (Firefox) und V8 (Chrome) verfolgen unterschiedliche Strategien bei der Bytecode-Erzeugung und den JIT-Stufen:
|
||||||
|
|
||||||
|
| Aspekt | Chrome (V8) | Firefox (SpiderMonkey) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **Pipeline** | Ignition (Interpreter) $\rightarrow$ Sparkplug $\rightarrow$ Maglev $\rightarrow$ Turbofan | C++ Interpreter $\rightarrow$ Baseline Interpreter $\rightarrow$ Baseline Compiler $\rightarrow$ WarpMonkey |
|
||||||
|
| **Bytecode-Layout** | V8 optimiert Jump-Targets früh im Bytecode-Generator; Basic Blocks werden linear angeordnet. | SpiderMonkey behält in frühen Phasen oft ein Bytecode-Layout bei, das näher an der Quellcode-Struktur liegt. |
|
||||||
|
| **Bailout / OSR** | Sehr aggressives Inlining und Dead-Code-Elimination im `Maglev`/`Turbofan`-Layer. | `WarpMonkey` nutzt Transpilation über CacheIR; je nach Verschachtelungstiefe können Scope- und Frame-Handling im Baseline-Tier minimal variieren. |
|
||||||
|
|
||||||
|
In **nicht-hochoptimiertem Code** (z. B. Skripte, die nur wenige Male laufen und im Interpreter bzw. Baseline JIT verbleiben):
|
||||||
|
|
||||||
|
* Verursacht tiefe Verschachtelung in manchen Engines zusätzlichen Overhead beim Verwalten von Lexical Environments/Scopes auf dem Stack.
|
||||||
|
* Early Returns erlauben es dem Interpreter, den aktuellen Stack-Frame schneller abzubauen, ohne tiefer liegende Scope-Hierarchien zu durchlaufen.
|
||||||
|
|
||||||
|
Sobald der Code jedoch "heiß" läuft (nach einigen tausend Iterationen), eliminieren sowohl Turbofan als auch WarpMonkey diesen Unterschied vollständig.
|
||||||
BIN
06_js-debug/unterricht/tag46/01_inversion/04_README.pdf
Normal file
BIN
06_js-debug/unterricht/tag46/01_inversion/04_README.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user