fix: single digit values now correctly position in rightmost column
The place-value system was only creating place values for non-zero digits, causing single digit values to appear in the leftmost column instead of the rightmost (ones place) when using multi-column abaci. Fix: Always create ALL place values from 0 to maxPlaceValue to ensure proper column distribution. For example: - value=3 with columns=3 now correctly shows 3 active beads in rightmost column - Empty places (tens, hundreds) correctly show inactive beads Updated tests to verify the fix works correctly. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -409,25 +409,16 @@ export function useAbacusPlaceStates(initialValue: number = 0, maxPlaceValue: Va
|
||||
const initializeFromValue = useCallback((value: number): PlaceStatesMap => {
|
||||
const states = new Map<ValidPlaceValues, PlaceState>();
|
||||
|
||||
if (value === 0) {
|
||||
// For zero, just initialize the ones place
|
||||
states.set(0, { placeValue: 0, heavenActive: false, earthActive: 0 });
|
||||
return states;
|
||||
}
|
||||
|
||||
// Extract digits for each place value - DIRECT CALCULATION
|
||||
let remainingValue = value;
|
||||
for (let place = 0; place <= maxPlaceValue && remainingValue > 0; place++) {
|
||||
// Always create ALL place values from 0 to maxPlaceValue (to match columns)
|
||||
for (let place = 0; place <= maxPlaceValue; place++) {
|
||||
const placeValueNum = Math.pow(10, place);
|
||||
const digit = Math.floor(remainingValue / placeValueNum) % 10;
|
||||
const digit = Math.floor(value / placeValueNum) % 10;
|
||||
|
||||
if (digit > 0 || place === 0) { // Always include ones place
|
||||
states.set(place as ValidPlaceValues, {
|
||||
placeValue: place as ValidPlaceValues,
|
||||
heavenActive: digit >= 5,
|
||||
earthActive: digit >= 5 ? digit - 5 : digit
|
||||
});
|
||||
}
|
||||
states.set(place as ValidPlaceValues, {
|
||||
placeValue: place as ValidPlaceValues,
|
||||
heavenActive: digit >= 5,
|
||||
earthActive: digit >= 5 ? digit - 5 : digit
|
||||
});
|
||||
}
|
||||
|
||||
return states;
|
||||
|
||||
101
packages/abacus-react/src/test/place-value-positioning.test.tsx
Normal file
101
packages/abacus-react/src/test/place-value-positioning.test.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { render } from '@testing-library/react';
|
||||
import { AbacusReact } from '../AbacusReact';
|
||||
|
||||
describe('Place Value Positioning', () => {
|
||||
it('should place single digit values in the rightmost column (ones place)', () => {
|
||||
// Test case: single digit 3 with 3 columns should show in rightmost column
|
||||
const { container } = render(
|
||||
<AbacusReact value={3} columns={3} interactive={true} />
|
||||
);
|
||||
|
||||
// Get all bead elements that are active
|
||||
const activeBeads = container.querySelectorAll('.abacus-bead.active');
|
||||
|
||||
// For value 3, we should have exactly 3 active earth beads (no heaven bead)
|
||||
expect(activeBeads).toHaveLength(3);
|
||||
|
||||
// The active beads should all be in the rightmost column (ones place = place value 0)
|
||||
activeBeads.forEach(bead => {
|
||||
const beadElement = bead as HTMLElement;
|
||||
// Check that the data-testid indicates place value 0 (rightmost/ones place)
|
||||
const testId = beadElement.getAttribute('data-testid');
|
||||
expect(testId).toMatch(/bead-place-0/); // Should be bead-place-0-earth-pos-{position}
|
||||
});
|
||||
});
|
||||
|
||||
it('should place two digit values correctly across columns', () => {
|
||||
// Test case: 23 with 3 columns
|
||||
// Should show: [empty][2][3] = [empty][tens][ones]
|
||||
const { container } = render(
|
||||
<AbacusReact value={23} columns={3} interactive={true} />
|
||||
);
|
||||
|
||||
const activeBeads = container.querySelectorAll('.abacus-bead.active');
|
||||
|
||||
// For value 23: 2 earth beads (tens) + 3 earth beads (ones) = 5 total
|
||||
expect(activeBeads).toHaveLength(5);
|
||||
|
||||
// Check that we have beads in place value 0 (ones) and place value 1 (tens)
|
||||
const placeZeroBeads = container.querySelectorAll('[data-testid*="bead-place-0-"]');
|
||||
const placeOneBeads = container.querySelectorAll('[data-testid*="bead-place-1-"]');
|
||||
const placeTwoBeads = container.querySelectorAll('[data-testid*="bead-place-2-"]');
|
||||
|
||||
// Should have beads for all 3 places (ones, tens, hundreds)
|
||||
expect(placeZeroBeads.length).toBeGreaterThan(0); // ones place should have beads
|
||||
expect(placeOneBeads.length).toBeGreaterThan(0); // tens place should have beads
|
||||
expect(placeTwoBeads.length).toBeGreaterThan(0); // hundreds place should have beads (but inactive)
|
||||
|
||||
// Count active beads in each place
|
||||
const activePlaceZero = container.querySelectorAll('[data-testid*="bead-place-0-"].active');
|
||||
const activePlaceOne = container.querySelectorAll('[data-testid*="bead-place-1-"].active');
|
||||
|
||||
expect(activePlaceZero).toHaveLength(3); // 3 active beads for ones
|
||||
expect(activePlaceOne).toHaveLength(2); // 2 active beads for tens
|
||||
});
|
||||
|
||||
it('should handle value 0 correctly in rightmost column', () => {
|
||||
const { container } = render(
|
||||
<AbacusReact value={0} columns={3} interactive={true} />
|
||||
);
|
||||
|
||||
// For value 0, no beads should be active
|
||||
const activeBeads = container.querySelectorAll('.abacus-bead.active');
|
||||
expect(activeBeads).toHaveLength(0);
|
||||
|
||||
// But there should still be beads in the ones place (place value 0)
|
||||
const placeZeroBeads = container.querySelectorAll('[data-testid*="bead-place-0-"]');
|
||||
expect(placeZeroBeads.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should maintain visual column ordering left-to-right as high-to-low place values', () => {
|
||||
// For value 147 with 3 columns: [1][4][7] = [hundreds][tens][ones]
|
||||
const { container } = render(
|
||||
<AbacusReact value={147} columns={3} interactive={true} />
|
||||
);
|
||||
|
||||
// Find the container element and check that beads are positioned correctly
|
||||
const svgElement = container.querySelector('svg');
|
||||
expect(svgElement).toBeTruthy();
|
||||
|
||||
// Check that place values appear in the correct visual order
|
||||
// This test verifies the column arrangement matches place value expectations
|
||||
const placeZeroBeads = container.querySelectorAll('[data-testid*="bead-place-0-"]');
|
||||
const placeOneBeads = container.querySelectorAll('[data-testid*="bead-place-1-"]');
|
||||
const placeTwoBeads = container.querySelectorAll('[data-testid*="bead-place-2-"]');
|
||||
|
||||
// All three places should have beads
|
||||
expect(placeZeroBeads.length).toBeGreaterThan(0); // ones
|
||||
expect(placeOneBeads.length).toBeGreaterThan(0); // tens
|
||||
expect(placeTwoBeads.length).toBeGreaterThan(0); // hundreds
|
||||
|
||||
// Check active bead counts match the digit values
|
||||
const activePlaceZero = container.querySelectorAll('[data-testid*="bead-place-0-"].active');
|
||||
const activePlaceOne = container.querySelectorAll('[data-testid*="bead-place-1-"].active');
|
||||
const activePlaceTwo = container.querySelectorAll('[data-testid*="bead-place-2-"].active');
|
||||
|
||||
expect(activePlaceZero).toHaveLength(3); // 7 ones = 1 heaven (5) + 2 earth = 3 active beads
|
||||
expect(activePlaceOne).toHaveLength(4); // 4 tens = 4 earth beads active
|
||||
expect(activePlaceTwo).toHaveLength(1); // 1 hundred = 1 earth bead active
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user