Skip to content

Commit b73c02f

Browse files
committed
With this commit we can add, delete, move and rotate elements
1 parent cc25a81 commit b73c02f

File tree

4 files changed

+221
-1
lines changed

4 files changed

+221
-1
lines changed

src/application/ElementService.js

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Position } from '../domain/valueObjects/Position.js';
2+
import { Element } from '../domain/entities/Element.js';
3+
4+
export class ElementService {
5+
/**
6+
* Creates a new element.
7+
*
8+
* @param {Function} ElementClass - The class of the element to create (e.g., Resistor, Junction).
9+
* @param {string} id - The unique identifier for the element.
10+
* @param {Position[]} terminals - The terminal positions.
11+
* @param {Object} propertiesArgs - The arguments for the specific properties container.
12+
* @returns {Element} The newly created element.
13+
*/
14+
static createElement(ElementClass, id, terminals, propertiesArgs) {
15+
return new ElementClass(id, terminals, ...propertiesArgs);
16+
}
17+
18+
/**
19+
* Deletes an element.
20+
*
21+
* @param {Element[]} elements - The array of elements.
22+
* @param {string} id - The unique identifier of the element to delete.
23+
* @returns {Element[]} The updated list of elements.
24+
*/
25+
static deleteElement(elements, id) {
26+
return elements.filter(element => element.id !== id);
27+
}
28+
29+
/**
30+
* Moves an element to a new position, updating all terminal positions.
31+
*
32+
* @param {Element} element - The element to move.
33+
* @param {Position} newReferencePosition - The new position for the reference terminal.
34+
*/
35+
static moveElement(element, newReferencePosition) {
36+
const refTerminal = element.terminals[0]; // Reference terminal
37+
const deltaX = newReferencePosition.x - refTerminal.x;
38+
const deltaY = newReferencePosition.y - refTerminal.y;
39+
40+
element.terminals = element.terminals.map(terminal =>
41+
new Position(terminal.x + deltaX, terminal.y + deltaY)
42+
);
43+
}
44+
45+
/**
46+
* Rotates an element to a new orientation.
47+
*
48+
* @param {Element} element - The element to rotate.
49+
* @param {number} newOrientation - The new orientation (0, 90, 180, or 270 degrees).
50+
*/
51+
static rotateElement(element, newOrientation) {
52+
if (![0, 90, 180, 270].includes(newOrientation)) {
53+
throw new Error("Orientation must be one of 0, 90, 180, or 270 degrees.");
54+
}
55+
56+
const refTerminal = element.terminals[0]; // Reference terminal
57+
const refX = refTerminal.x;
58+
const refY = refTerminal.y;
59+
60+
element.terminals = element.terminals.map((terminal, index) => {
61+
if (index === 0) return terminal; // Keep the reference terminal unchanged
62+
63+
// Calculate relative position
64+
const relX = terminal.x - refX;
65+
const relY = terminal.y - refY;
66+
67+
// Rotate relative position
68+
const [newRelX, newRelY] = ElementService._rotatePosition(relX, relY, newOrientation);
69+
70+
// Return the new absolute position
71+
return new Position(refX + newRelX, refY + newRelY);
72+
});
73+
}
74+
75+
/**
76+
* Rotates a position around the origin.
77+
*
78+
* @param {number} x - The x-coordinate of the position.
79+
* @param {number} y - The y-coordinate of the position.
80+
* @param {number} angle - The angle to rotate by (in degrees).
81+
* @returns {number[]} The rotated [x, y] position.
82+
*/
83+
static _rotatePosition(x, y, angle) {
84+
const radians = (Math.PI / 180) * angle;
85+
const cos = Math.cos(radians);
86+
const sin = Math.sin(radians);
87+
const newX = Math.round(x * cos - y * sin);
88+
const newY = Math.round(x * sin + y * cos);
89+
return [newX, newY];
90+
}
91+
}

tests/domain/Element.test.js

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Position } from '../../src/domain/valueObjects/Position.js';
44
import { Label } from '../../src/domain/valueObjects/Label.js';
55
import { Properties } from '../../src/domain/valueObjects/Properties.js';
66

7-
console.log("This test runs successfully")
87

98
describe('Basic Import Test', () => {
109
it('should import Element successfully', () => {

tests/domain/ElementService.test.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { expect } from 'chai';
2+
import { ElementService } from '../../src/application/ElementService.js';
3+
import { MockElement } from './MockElement.js'; // A mock element class for testing
4+
import { Position } from '../../src/domain/valueObjects/Position.js';
5+
import { Properties } from '../../src/domain/valueObjects/Properties.js';
6+
import { Label } from '../../src/domain/valueObjects/Label.js';
7+
8+
describe('Element Service Tests', () => {
9+
describe('Element Creation', () => {
10+
it('should create an element successfully', () => {
11+
const terminals = [new Position(10, 20), new Position(30, 40)];
12+
const properties = new Properties({ resistance: 100 });
13+
const element = ElementService.createElement(MockElement, 'E1', terminals, [null, properties]);
14+
15+
expect(element).to.be.instanceOf(MockElement);
16+
expect(element.id).to.equal('E1');
17+
expect(element.terminals).to.deep.equal(terminals);
18+
expect(element.properties).to.equal(properties);
19+
});
20+
21+
it('should throw an error if creation inputs are invalid', () => {
22+
expect(() => ElementService.createElement(MockElement, 'E2', [10, 20], [null, new Properties()])).to.throw(
23+
"Terminals must be an array of Position instances."
24+
);
25+
});
26+
});
27+
28+
describe('Element Deletion', () => {
29+
it('should delete an element successfully', () => {
30+
const terminals = [new Position(10, 20)];
31+
const properties = new Properties();
32+
const element = new MockElement('E3', terminals, null, properties);
33+
34+
let elements = [element];
35+
elements = ElementService.deleteElement(elements, 'E3');
36+
37+
expect(elements).to.be.an('array').that.is.empty;
38+
});
39+
40+
it('should do nothing when deleting a non-existent element', () => {
41+
const terminals = [new Position(10, 20)];
42+
const properties = new Properties();
43+
const element = new MockElement('E4', terminals, null, properties);
44+
45+
let elements = [element];
46+
elements = ElementService.deleteElement(elements, 'NonExistentID');
47+
48+
expect(elements).to.have.lengthOf(1);
49+
expect(elements[0].id).to.equal('E4');
50+
});
51+
});
52+
53+
describe('Element Movement', () => {
54+
it('should move an element successfully', () => {
55+
const terminals = [new Position(10, 20), new Position(30, 40)];
56+
const element = new MockElement('E5', terminals, null, new Properties());
57+
58+
ElementService.moveElement(element, new Position(20, 30));
59+
60+
expect(element.terminals).to.deep.equal([
61+
new Position(20, 30),
62+
new Position(40, 50),
63+
]);
64+
});
65+
66+
it('should not change terminals if moved to the same position', () => {
67+
const terminals = [new Position(10, 20), new Position(30, 40)];
68+
const element = new MockElement('E6', terminals, null, new Properties());
69+
70+
ElementService.moveElement(element, new Position(10, 20));
71+
72+
expect(element.terminals).to.deep.equal(terminals);
73+
});
74+
});
75+
76+
describe('Element Rotation', () => {
77+
it('should rotate an element by 90 degrees', () => {
78+
const terminals = [new Position(10, 10), new Position(20, 10)];
79+
const element = new MockElement('E7', terminals, null, new Properties());
80+
81+
ElementService.rotateElement(element, 90);
82+
83+
expect(element.terminals).to.deep.equal([
84+
new Position(10, 10), // Reference terminal remains unchanged
85+
new Position(10, 20), // Rotated position
86+
]);
87+
});
88+
89+
it('should rotate an element by 180 degrees', () => {
90+
const terminals = [new Position(10, 10), new Position(20, 10)];
91+
const element = new MockElement('E8', terminals, null, new Properties());
92+
93+
ElementService.rotateElement(element, 180);
94+
95+
expect(element.terminals).to.deep.equal([
96+
new Position(10, 10), // Reference terminal remains unchanged
97+
new Position(0, 10), // Rotated position
98+
]);
99+
});
100+
101+
it('should throw an error for invalid rotation angles', () => {
102+
const terminals = [new Position(10, 10), new Position(20, 10)];
103+
const element = new MockElement('E9', terminals, null, new Properties());
104+
105+
expect(() => ElementService.rotateElement(element, 45)).to.throw(
106+
"Orientation must be one of 0, 90, 180, or 270 degrees."
107+
);
108+
});
109+
});
110+
});

tests/domain/MockElement.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Element } from '../../src/domain/entities/Element.js';
2+
import { Properties } from '../../src/domain/valueObjects/Properties.js';
3+
4+
/**
5+
* MockElement class extends the Element class for testing purposes.
6+
*/
7+
export class MockElement extends Element {
8+
/**
9+
* Creates an instance of MockElement.
10+
*
11+
* @param {string} id - The unique identifier for the element.
12+
* @param {Position[]} terminals - The list of terminal positions.
13+
* @param {Label|null} label - The label of the element (optional).
14+
* @param {Properties} properties - A container for the element's specific properties.
15+
*/
16+
constructor(id, terminals = [], label = null, properties = new Properties()) {
17+
super(id, terminals, label, properties);
18+
this.type = 'mock';
19+
}
20+
}

0 commit comments

Comments
 (0)