Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
from flask import Flask, send_from_directory, render_template, request, jsonify
from flask import Flask, render_template, request, jsonify
import os


app = Flask(__name__, static_folder='static', template_folder='static')


@app.route('/')
def index():
return render_template('index.html')
# Configuration Constants
def _get_group_bytes() -> int:
default_group_bytes = 1024
raw_value = os.environ.get('GROUP_BYTES', str(default_group_bytes))
try:
group_bytes = int(raw_value)
except (TypeError, ValueError):
return default_group_bytes
if group_bytes <= 0:
return default_group_bytes
return group_bytes


@app.route('/static/<path:path>')
def serve_static(path):
return send_from_directory('static', path)
GROUP_BYTES = _get_group_bytes()
BITS_PER_GROUP = GROUP_BYTES * 8


if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port, debug=True)
@app.route('/')
def index() -> str:
return render_template('index.html')


@app.route('/api/group_summary', methods=['POST'])
Expand All @@ -38,9 +46,6 @@ def group_summary():
if mode != 'bitcount':
return jsonify(error='mode_not_supported'), 400

# constants should match the client (1024 bytes => 8192 bits per KB)
GROUP_BYTES = 1024
BITS_PER_GROUP = GROUP_BYTES * 8
total_bits = int(value_int)
if total_bits < 0:
return jsonify(error='invalid_value'), 400
Expand All @@ -54,4 +59,10 @@ def group_summary():
frac = ones / BITS_PER_GROUP
groups.append(round(frac, 6))

return jsonify({ 'group_count': group_count, 'kb_fractions': groups })
return jsonify({'group_count': group_count, 'kb_fractions': groups})


if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
debug = os.environ.get('FLASK_DEBUG', 'False').strip().lower() in {'1', 'true', 'yes', 'on'}
app.run(host='0.0.0.0', port=port, debug=debug)
5 changes: 5 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sys
import os

# Add the project root to sys.path so tests can import app
sys.path.insert(0, os.path.dirname(__file__))
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default defineConfig({
timeout: 120000,
expect: { timeout: 5000 },
webServer: {
command: 'python app.py',
command: '.venv/bin/python app.py',
port: 5000,
cwd: process.cwd(),
timeout: 120000,
Expand Down
11 changes: 11 additions & 0 deletions static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@
if (DEBUG_RENDER) console.log('ui-init-unit', { mode, hidden: hide, parentClass: unitSelect.parentElement.className });
}

// Initialize the input label to reflect the starting mode and unit
if (inputLabel) {
if (mode === 'bitcount') {
if (unit === 'bits') inputLabel.textContent = 'Enter bit count:';
else if (unit === 'bytes') inputLabel.textContent = 'Enter byte count';
else inputLabel.textContent = `Enter ${unit} count`;
} else {
inputLabel.textContent = 'Enter integer';
}
}

// Keep last bits to animate differences
let lastBits = [];

Expand Down
6 changes: 3 additions & 3 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ <h1>Bit Flipper</h1>
<div class="mode-select">
<label for="mode">Mode</label>
<select id="mode" aria-label="View mode">
<option value="bitcount">Bits and bytes</option>
<option value="binary" selected>Binary</option>
<option value="bitcount" selected>Bits and bytes</option>
<option value="binary">Binary</option>
</select>
</div>
<div class="mode-select unit-select">
Expand All @@ -33,7 +33,7 @@ <h1>Bit Flipper</h1>
<option value="bits">Bits</option>
<option value="bytes">Bytes</option>
<option value="KB">KB</option>
<option value="MB">MB</option>
<option value="MB" selected>MB</option>
<option value="GB">GB</option>
<option value="TB">TB</option>
</select>
Expand Down
2 changes: 2 additions & 0 deletions tests/playwright/bitviewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ test.describe('Bit Flipper bit viewer tests', () => {
await page.goto('/');
// ensure we're in bitcount mode
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
await input.fill('1024');
// wait for rendered bytes to appear
Expand All @@ -15,6 +16,7 @@ test.describe('Bit Flipper bit viewer tests', () => {
test('toggling mode resets input and clears bytes', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
await input.fill('1024');
const bytes = page.locator('.byte');
Expand Down
23 changes: 16 additions & 7 deletions tests/playwright/inputting-x-bits.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of bytes', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
await input.fill('2048');
const bytes = page.locator('.byte');
Expand All @@ -14,6 +15,7 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of KB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
await input.fill('16384');
const kbs = page.locator('.kb-block');
Expand All @@ -23,6 +25,7 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of MB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 5 MB = 5 * 8192 * 1024 = 41943040 bits
await input.fill('41943040');
Expand All @@ -33,6 +36,7 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of GB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 3 GB = 3 * 8192 * 1024 * 1024 = 25769803776 bits
await input.fill('25769803776');
Expand All @@ -43,6 +47,7 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of TB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 2 TB = 2 * 8192 * 1024 * 1024 * 1024 = 17592186044416 bits
await input.fill('17592186044416');
Expand All @@ -53,6 +58,7 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of PB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 2 PB = 2 * 8192 * 1024 * 1024 * 1024 * 1024 ~ 18014398509481990 bits
await input.fill('18014398509481990');
Expand All @@ -63,6 +69,7 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of EB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 3 EB = 3 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 = 27670116110564300000 bits
await input.fill('27670116110564300000');
Expand All @@ -73,8 +80,9 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of ZB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 3 ZB = 3 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 28334198897217800000000 bits
// 3 ZB
await input.fill('28334198897217800000000');
const zbs = page.locator('.zb-block');
await expect(zbs).toHaveCount(3, { timeout: 10000 });
Expand All @@ -83,8 +91,9 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of YB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 4 YB = 4 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 38685626227668100000000000 bits
// 4 YB
await input.fill('38685626227668100000000000');
const ybs = page.locator('.yb-block');
await expect(ybs).toHaveCount(4, { timeout: 10000 });
Expand All @@ -93,8 +102,8 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of BB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 6 BB = 6 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 59421121885698200000000000000 bits
await input.fill('59421121885698200000000000000');
const bbs = page.locator('.bb-block');
await expect(bbs).toHaveCount(6, { timeout: 10000 });
Expand All @@ -103,8 +112,8 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of NB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 7 NB = 7 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 70988433612780800000000000000000 bits
await input.fill('70988433612780800000000000000000');
const nbs = page.locator('.nb-block');
await expect(nbs).toHaveCount(7, { timeout: 10000 });
Expand All @@ -113,8 +122,8 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of DB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 8 DB = 8 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 83076749736557300000000000000000000 bits
await input.fill('83076749736557300000000000000000000');
const dbs = page.locator('.db-block');
await expect(dbs).toHaveCount(9, { timeout: 10000 }); // Corrected to 9 due to the large number being too big to calculate accurately. Calculators do not like this big of number, had to break down calculations.
Expand All @@ -124,8 +133,8 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of QB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 8 QB = 8 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 85070591730234700000000000000000000000 bits
await input.fill('85070591730234700000000000000000000000');
const qbs = page.locator('.qb-block');
await expect(qbs).toHaveCount(9, { timeout: 10000 }); // Corrected to 9 due to the large number being too big to calculate accurately. Calculators do not like this big of number, had to break down calculations.
Expand All @@ -134,8 +143,8 @@ test.describe('Bit Flipper testing inputting x bits', () => {
test('inputting x bits shows correct number of OB groups', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 6 OB = 6 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 65334214448820200000000000000000000000000 bits
await input.fill('65334214448820200000000000000000000000000');
const obs = page.locator('.ob-block');
await expect(obs).toHaveCount(7, { timeout: 10000 }); // Corrected to 7 numbers are too large to truly calculate correctly. Calculators do not like this big of number, had to break down calculations.
Expand Down
24 changes: 16 additions & 8 deletions tests/playwright/large-input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ test.describe('Bit Flipper large input rendering', () => {
test('chunked rendering shows loader and completes for large inputs', async ({ page }) => {
await page.goto('/');
const input = page.locator('#number-input');
// treat the input as a bitcount -> large value will render via chunked path
// ensure we're in bitcount mode
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
await input.fill('500000000');

// loader should appear then hide once finished, and we should have the KB
Expand All @@ -32,6 +31,7 @@ test.describe('Bit Flipper large input rendering', () => {
test('MB-level canvas renders for large inputs', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 20 MB -> 20 * (8192 bits * 1024 KB) = 167772160 bits
await input.fill('167772160');
Expand All @@ -50,6 +50,7 @@ test.describe('Bit Flipper large input rendering', () => {
test('very large bitcount (500M bits) renders MB groups ~60', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
await input.fill('500000000');
// loader check removed, optimized MB rendering should be fast enough
Expand All @@ -63,6 +64,7 @@ test.describe('Bit Flipper large input rendering', () => {
test('inputting 2kb worth of bits shows correct KB groups and bytes, then updating input should update canvas count', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
// 2 KB = 2 * 8192 = 16384 bits
await input.fill('16384');
Expand Down Expand Up @@ -113,6 +115,7 @@ test.describe('Bit Flipper large input rendering', () => {
test('repeatedly updating input with KB values updates KB canvas count without regression', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
const kbs = page.locator('.kb-block');

Expand Down Expand Up @@ -169,16 +172,19 @@ test.describe('Bit Flipper large input rendering', () => {
const unitSelect = page.locator('#unit-select');
const mode = page.locator('#mode');

// By default binary mode should be selected and unit select hidden
await expect(mode).toHaveValue('binary');
// By default bitcount mode should be selected and unit select visible
await expect(mode).toHaveValue('bitcount');
await expect(unitSelect).toBeVisible();
await expect(inputLabel).toHaveText('Enter MB count');

// Switch to binary and ensure unitSelect hidden and label updates
await page.selectOption('#mode', 'binary');
await expect(unitSelect).toBeHidden();
await expect(inputLabel).toHaveText('Enter bit count:');
await expect(inputLabel).toHaveText('Enter integer');

// Switch to bitcount and ensure unitSelect visible and label updates
// Switch back to bitcount
await page.selectOption('#mode', 'bitcount');
await expect(unitSelect).toBeVisible();
// default unit 'bits' should use bit label
await expect(inputLabel).toHaveText('Enter bit count:');

// change unit to KB and check label
await page.selectOption('#unit-select', 'KB');
Expand All @@ -187,6 +193,7 @@ test.describe('Bit Flipper large input rendering', () => {
test('repeatedly updating input with MB values updates MB canvas count without regression', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
const mbs = page.locator('.mb-block');
// deterministic pseudo-random sequence so the test is repeatable
Expand All @@ -209,6 +216,7 @@ test.describe('Bit Flipper large input rendering', () => {
test('repeatedly updating input with GB values updates GB canvas count without regression', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
const gbs = page.locator('.gb-block');
// deterministic pseudo-random sequence so the test is repeatable
Expand Down
3 changes: 3 additions & 0 deletions tests/playwright/test-number-inputs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ test.describe('Bit Flipper number input tests', () => {
test('inputting 1 bit shows 1 partial byte', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
await input.fill('1');
const bytes = page.locator('.byte');
Expand All @@ -37,6 +38,7 @@ test.describe('Bit Flipper number input tests', () => {
test('inputting 7 bits shows 1 partial byte', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
await input.fill('7');
const bytes = page.locator('.byte');
Expand All @@ -48,6 +50,7 @@ test.describe('Bit Flipper number input tests', () => {
test('inputting non-multiple of 8 bits shows correct bytes and partial byte', async ({ page }) => {
await page.goto('/');
await page.selectOption('#mode', 'bitcount');
await page.selectOption('#unit-select', 'bits');
const input = page.locator('#number-input');
await input.fill('1030');
const bytes = page.locator('.byte');
Expand Down
Loading