From ec7e7546d5679290440fafd67966573aa94051cf Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Sun, 23 Apr 2023 06:41:40 +0000 Subject: [PATCH 1/7] change circle movement with a subset of constrained points --- cypress/e2e/DoenetML/tagSpecific/circle.cy.js | 4232 ++++++++++++++++- src/Core/Core.js | 11 + src/Core/components/Circle.js | 183 +- 3 files changed, 4405 insertions(+), 21 deletions(-) diff --git a/cypress/e2e/DoenetML/tagSpecific/circle.cy.js b/cypress/e2e/DoenetML/tagSpecific/circle.cy.js index 1bd52a0e4d..c868735bab 100644 --- a/cypress/e2e/DoenetML/tagSpecific/circle.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/circle.cy.js @@ -14416,9 +14416,21 @@ describe("Circle Tag Tests", function () { cy.log("move circle"); cy.window().then(async (win) => { let desiredHeight = 5; - let actualHeight = (5 + 2) / 2; - // given previous radius is 2, would move through point to 5+2, - // so that center of circle would be (5+2)/2 + let actualHeight = 11 / 4; + // The following isn't the desired behavior, but it is a result of the situation + // appearing to be that of a constrained center and a free through point when moving the circle. + // (The through point ends up where requested but the center got altered.) + // Since we care about that situation (see test "circle with center and through point, center constrained") + // but don't care as much about this contrived situation, + // we live with this more complicated behavior in the case where we have this strange relationship + // between the through point and the center. + // The attempt to move the through point a second time to preserve the radius yield this result: + // Given previous radius is 2, would move through point to (-3, 5+2), + // so that center of circle would be initially be (-3,(5+2)/2). + // Since center changed from given value but through point didn't, + // it will attempt to move through point back to radius 2 above center, + // i.e., to (-3, (5+2)/2+2)) = (-3, 11/2) + // which will make the center be (-3, 11/4) await win.callAction1({ actionName: "moveCircle", componentName: "/_circle1", @@ -14488,8 +14500,14 @@ describe("Circle Tag Tests", function () { cy.log("move circle below x-axis"); cy.window().then(async (win) => { - let desiredHeight = -8; - let actualHeight = (-8 + 7) / 2; // given previous radius is 7 + let desiredHeight = -31; + let actualHeight = -5 / 2; + // Given previous radius is 7, would move through point to (4, -24), + // so that center of circle would be initially be (4,-12). + // Since center changed from given value but through point didn't, + // it will attempt to move through point back to radius 7 above center, + // i.e., to (4, -5) + // which will make the center be (4, -5/2) await win.callAction1({ actionName: "moveCircle", componentName: "/_circle1", @@ -14600,9 +14618,21 @@ describe("Circle Tag Tests", function () { cy.log("move circle"); cy.window().then(async (win) => { let desiredHeight = 5; - let actualHeight = (5 + 2) / 2; - // given previous radius is 2, would move through point to 5+2, - // so that center of circle would be (5+2)/2 + let actualHeight = 11 / 4; + // The following isn't the desired behavior, but it is a result of the situation + // appearing to be that of a constrained center and a free through point when moving the circle. + // (The through point ends up where requested but the center got altered.) + // Since we care about that situation (see test "circle with center and through point, center constrained") + // but don't care as much about this contrived situation, + // we live with this more complicated behavior in the case where we have this strange relationship + // between the through point and the center. + // The attempt to move the through point a second time to preserve the radius yield this result: + // Given previous radius is 2, would move through point to (-3, 5+2), + // so that center of circle would be initially be (-3,(5+2)/2). + // Since center changed from given value but through point didn't, + // it will attempt to move through point back to radius 2 above center, + // i.e., to (-3, (5+2)/2+2)) = (-3, 11/2) + // which will make the center be (-3, 11/4) await win.callAction1({ actionName: "moveCircle", componentName: "/_circle1", @@ -14672,8 +14702,14 @@ describe("Circle Tag Tests", function () { cy.log("move circle below x-axis"); cy.window().then(async (win) => { - let desiredHeight = -8; - let actualHeight = (-8 + 7) / 2; // given previous radius is 7 + let desiredHeight = -31; + let actualHeight = -5 / 2; + // Given previous radius is 7, would move through point to (4, -24), + // so that center of circle would be initially be (4,-12). + // Since center changed from given value but through point didn't, + // it will attempt to move through point back to radius 7 above center, + // i.e., to (4, -5) + // which will make the center be (4, -5/2) await win.callAction1({ actionName: "moveCircle", componentName: "/_circle1", @@ -17718,4 +17754,4180 @@ describe("Circle Tag Tests", function () { ); cy.get(cesc("#\\/Cfilldescrip")).should("have.text", "C has a white fill."); }); + + it("circle with center and through point, center constrained", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,4) + + + + + (5,6) + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + let cnx = 3, + cny = 4; + let tx = 5, + ty = 6; + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(cnx, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(cny, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + let cnx = 3, + cny = 4; + let tx = 5, + ty = 6; + + cy.log("move circle"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = -2, + dy = -6; + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + tx += cnx - desiredcnx; + ty += cny - desiredcny; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move defining center"); + cy.window().then(async (win) => { + let desiredcnx = -5; + let desiredcny = 5; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point1", + args: { x: desiredcnx, y: desiredcny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move reffed center"); + cy.window().then(async (win) => { + let desiredcnx = 1; + let desiredcny = -1; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: desiredcnx, y: desiredcny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move through point"); + cy.window().then(async (win) => { + tx = -4; + ty = 3; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point2", + args: { x: tx, y: ty }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("change reffed radius"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + r = r / 4; + + tx = cnx + (tx - cnx) / 4; + ty = cny + (ty - cny) / 4; + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point3", + args: { x: r, y: 0 }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle2"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = 4, + dy = -1; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + tx += cnx - desiredcnx; + ty += cny - desiredcny; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = -5, + dy = 4; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + tx += cnx - desiredcnx; + ty += cny - desiredcny; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + }); + + it("circle with center and through point, through point constrained", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,4) + + (5,7) + + + + + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + let cnx = 3, + cny = 4; + let tx = 6, + ty = 8; + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(cnx, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(cny, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + let cnx = 3, + cny = 4; + let tx = 6, + ty = 8; + + cy.log("move circle"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = -2, + dy = -6; + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + tx += cnx - desiredcnx; + ty += cny - desiredcny; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move defining center"); + cy.window().then(async (win) => { + cnx = -5; + cny = 5; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point1", + args: { x: cnx, y: cny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move reffed center"); + cy.window().then(async (win) => { + cnx = 1; + cny = -1; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: cnx, y: cny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move through point"); + cy.window().then(async (win) => { + let desiredtx = -4; + let desiredty = 3; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point2", + args: { x: desiredtx, y: desiredty }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("change reffed radius"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let desiredr = r / 4; + + let desiredtx = cnx + (tx - cnx) / 4; + let desiredty = cny + (ty - cny) / 4; + + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point3", + args: { x: desiredr, y: 0 }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle2"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = 4, + dy = -1; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + + let desiredtx = tx; + let desiredty = ty; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + cnx += tx - desiredtx; + cny += ty - desiredty; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = -5, + dy = 4; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + + let desiredtx = tx; + let desiredty = ty; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + cnx += tx - desiredtx; + cny += ty - desiredty; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + }); + + it("circle through two points, one point constrained", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + + (2,-3) + + + + + (3,4) + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + let t1x = 3, + t1y = -2; + let t2x = 3, + t2y = 4; + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(t1x, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(t1y, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(t2x, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(t2y, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(cnx)},${nInDOM(cny)})`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + cy.log("move circle"); + cy.window().then(async (win) => { + let dx = -2, + dy = -7; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let desiredcnx = (t1x + t2x) / 2; + let desiredcny = (t1y + t2y) / 2; + + let desiredt1x = t1x; + let desiredt1y = t1y; + + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + + t2x += t1x - desiredt1x; + t2y += t1y - desiredt1y; + + let cnx = (t1x + t2x) / 2; + let cny = (t1y + t2y) / 2; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(cnx)},${nInDOM(cny)})`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move first through point"); + cy.window().then(async (win) => { + let desiredt1x = 4, + desiredt1y = -1; + + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point1", + args: { x: desiredt1x, y: desiredt1y }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(cnx)},${nInDOM(cny)})`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move second through point"); + cy.window().then(async (win) => { + t2x = 8; + t2y = -3; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point2", + args: { x: t2x, y: t2y }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(cnx)},${nInDOM(cny)})`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move center"); + cy.window().then(async (win) => { + let dx = 2, + dy = -3; + + let desiredcnx = (t1x + t2x) / 2 + dx; + let desiredcny = (t1y + t2y) / 2 + dy; + + t1x = Math.round((t1x + dx) / 3) * 3; + t1y = Math.round((t1y + dy) / 2) * 2; + + t2x += dx; + t2y += dy; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: desiredcnx, y: desiredcny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(cnx)},${nInDOM(cny)})`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move radius to half size"); + cy.window().then(async (win) => { + let desiredr = + Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 4; + + let desiredcnx = (t1x + t2x) / 2; + let desiredcny = (t1y + t2y) / 2; + + let desiredt1x = desiredcnx + (t1x - desiredcnx) / 2; + let desiredt1y = desiredcny + (t1y - desiredcny) / 2; + + t2x = desiredcnx + (t2x - desiredcnx) / 2; + t2y = desiredcny + (t2y - desiredcny) / 2; + + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point3", + args: { x: desiredr, y: 0 }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(cnx)},${nInDOM(cny)})`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle2"); + cy.window().then(async (win) => { + let dx = -8; + let dy = 5; + + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + + let desiredt1x = t1x; + let desiredt1y = t1y; + + let desiredcnx = (desiredt1x + t2x) / 2; + let desiredcny = (desiredt1y + t2y) / 2; + + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + + t2x += t1x - desiredt1x; + t2y += t1y - desiredt1y; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(cnx)},${nInDOM(cny)})`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let dx = -3; + let dy = 3; + + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + + let desiredt1x = t1x; + let desiredt1y = t1y; + + let desiredcnx = (desiredt1x + t2x) / 2; + let desiredcny = (desiredt1y + t2y) / 2; + + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + + t2x += t1x - desiredt1x; + t2y += t1y - desiredt1y; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(cnx)},${nInDOM(cny)})`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + }); + + it("circle through three points, one point constrained", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (2,-3) + (3,4) + + + + + (-3,4) + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + let t1x = 2, + t1y = -3; + let t2x = 3, + t2y = 4; + let t3x = -3, + t3y = 4; + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/_circle1"].stateValues.numericalCenter[0]; + let cny = stateVariables["/_circle1"].stateValues.numericalCenter[1]; + let r = stateVariables["/_circle1"].stateValues.numericalRadius; + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(t1x, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(t1y, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(t2x, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(t2y, 1e-12); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo(t3x, 1e-12); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo(t3y, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + cy.log("move circle up and to the right"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/_circle1"].stateValues.numericalCenter[0]; + let cny = stateVariables["/_circle1"].stateValues.numericalCenter[1]; + let r = stateVariables["/_circle1"].stateValues.numericalRadius; + + let desireddx = 5, + desireddy = 3; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = 6; + let dy = 4; + + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); + }); + }); + + cy.log("move circle2"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/graph3/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph3/circle"].stateValues.numericalCenter[1]; + let r = stateVariables["/graph3/circle"].stateValues.numericalRadius; + + let desireddx = -5, + desireddy = -2.2; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = -6; + let dy = -2; + + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/graph4/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph4/circle"].stateValues.numericalCenter[1]; + let r = stateVariables["/graph4/circle"].stateValues.numericalRadius; + + let desireddx = 7, + desireddy = -3; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = 6; + let dy = -2; + + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; + + cnx = Math.round(cnx * 1e14) / 1e14; + cny = Math.round(cny * 1e14) / 1e14; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); + }); + }); + }); + + it("circle through three points, two points constrained", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (2,-3) + (3,4) + + + + + (-3,4) + + + + + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + let t1x = 2, + t1y = -3; + let t2x = 3, + t2y = 4; + let t3x = -3, + t3y = 4; + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/_circle1"].stateValues.numericalCenter[0]; + let cny = stateVariables["/_circle1"].stateValues.numericalCenter[1]; + let r = stateVariables["/_circle1"].stateValues.numericalRadius; + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(t1x, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(t1y, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(t2x, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(t2y, 1e-12); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo(t3x, 1e-12); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo(t3y, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + cy.log("move circle up and to the right"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/_circle1"].stateValues.numericalCenter[0]; + let cny = stateVariables["/_circle1"].stateValues.numericalCenter[1]; + let r = stateVariables["/_circle1"].stateValues.numericalRadius; + + let desireddx = 5, + desireddy = 3; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = 6; + let dy = 4; + + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); + }); + }); + + cy.log("move circle2"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/graph3/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph3/circle"].stateValues.numericalCenter[1]; + let r = stateVariables["/graph3/circle"].stateValues.numericalRadius; + + let desireddx = -4.9, + desireddy = -2.2; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = -6; + let dy = -2; + + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/graph4/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph4/circle"].stateValues.numericalCenter[1]; + let r = stateVariables["/graph4/circle"].stateValues.numericalRadius; + + let desireddx = 7.1, + desireddy = -2.9; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = 6; + let dy = -2; + + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; + + cnx = Math.round(cnx * 1e14) / 1e14; + cny = Math.round(cny * 1e14) / 1e14; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, + 1e-12, + ); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); + }); + }); + }); }); diff --git a/src/Core/Core.js b/src/Core/Core.js index 861e95e4b2..2df99925ac 100644 --- a/src/Core/Core.js +++ b/src/Core/Core.js @@ -83,6 +83,7 @@ export default class Core { performAction: this.performAction.bind(this), resolveAction: this.resolveAction.bind(this), triggerChainedActions: this.triggerChainedActions.bind(this), + updateRenderers: this.updateRenderers.bind(this), requestRecordEvent: this.requestRecordEvent.bind(this), requestAnimationFrame: this.requestAnimationFrame.bind(this), cancelAnimationFrame: this.cancelAnimationFrame.bind(this), @@ -9260,6 +9261,16 @@ export default class Core { } } + async updateRenderers({ + actionId, + sourceInformation = {}, + skipRendererUpdate = false, + }) { + if (!skipRendererUpdate) { + await this.updateAllChangedRenderers(sourceInformation, actionId); + } + } + async requestUpdate({ updateInstructions, transient = false, diff --git a/src/Core/components/Circle.js b/src/Core/components/Circle.js index 1da9beca2a..12b0a28983 100644 --- a/src/Core/components/Circle.js +++ b/src/Core/components/Circle.js @@ -1237,7 +1237,7 @@ export default class Circle extends Curve { if (pt === undefined) { // if dependencies haven't been updated, // it is possible to temporarility have fewer numericalThroughPoints - // than nThroughPOints + // than nThroughPoints return { setValue: { numericalRadius: NaN } }; } let numericalRadius = Math.sqrt( @@ -1586,7 +1586,7 @@ export default class Circle extends Curve { if (globalDependencyValues.numericalThroughPoints.length < 1) { // if dependencies haven't been updated, // it is possible to temporarility have fewer numericalThroughPoints - // than nThroughPOints + // than nThroughPoints return { setValue: { numericalCenter: [NaN, NaN] } }; } @@ -1601,7 +1601,7 @@ export default class Circle extends Curve { if (globalDependencyValues.numericalThroughPoints.length < 2) { // if dependencies haven't been updated, // it is possible to temporarility have fewer numericalThroughPoints - // than nThroughPOints + // than nThroughPoints return { setValue: { numericalCenter: [NaN, NaN] } }; } @@ -2528,7 +2528,7 @@ export default class Circle extends Curve { let numericalPrescribedCenter = await this.stateValues .numericalPrescribedCenter; - if (nThroughPoints <= 1 || numericalPrescribedCenter !== null) { + if (nThroughPoints <= 1 || numericalPrescribedCenter.length > 0) { instructions.push({ updateType: "updateValue", componentName: this.componentName, @@ -2537,16 +2537,16 @@ export default class Circle extends Curve { }); } + let numericalThroughPoints = []; + if (nThroughPoints >= 1) { // set numerical through points for two reasons - // 1. if have cricle prescribed by center and one point + // 1. if have circle prescribed by center and one point // need to move the point to preserve the radius // 2. if have through points that are constrained/attracted // to objects, set through points to attempt to keep their relative // positions constant even as they get adjusted by the constraints - let numericalThroughPoints = []; - if (throughAngles === undefined) { throughAngles = await this.stateValues.throughAngles; } @@ -2571,20 +2571,21 @@ export default class Circle extends Curve { }); } + // move circle if (transient) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: instructions, transient, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, }); } else { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: instructions, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -2597,6 +2598,166 @@ export default class Circle extends Curve { }, }); } + + // if circle is based on more than 1 point (center or throughpoints) + // and a subset of those points appear to be constrained while preserving their relationship + // then move the other points in attempt to preserve their relationship with the constrained points, + // which will keep the radius of the circle fixed + + let resultingCenter = await this.stateValues.numericalCenter; + let resultingNumericalThroughPoints = await this.stateValues + .numericalThroughPoints; + + if (numericalPrescribedCenter.length > 0 && nThroughPoints === 1) { + // center and one through point + + let throughPointUnchanged = numericalThroughPoints[0].every( + (v, i) => v === resultingNumericalThroughPoints[0][i], + ); + + let centerUnchanged = center.every((v, i) => v === resultingCenter[i]); + + if (throughPointUnchanged && !centerUnchanged) { + let theta = throughAngles[0]; + let newNumericalThroughPoints = [ + [ + resultingCenter[0] + radius * Math.cos(theta), + resultingCenter[1] + radius * Math.sin(theta), + ], + ]; + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "numericalThroughPoints", + value: newNumericalThroughPoints, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } else if (centerUnchanged && !throughPointUnchanged) { + let theta = throughAngles[0]; + let newCenter = [ + resultingNumericalThroughPoints[0][0] - radius * Math.cos(theta), + resultingNumericalThroughPoints[0][1] - radius * Math.sin(theta), + ]; + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "numericalCenter", + value: newCenter, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } else if (nThroughPoints >= 2) { + let throughPointsChanged = []; + let nThroughPointsChanged = 0; + + for (let [ind, pt] of numericalThroughPoints.entries()) { + if ( + !pt.every((v, i) => v === resultingNumericalThroughPoints[ind][i]) + ) { + throughPointsChanged.push(ind); + nThroughPointsChanged++; + } + } + + if (nThroughPointsChanged > 0 && nThroughPointsChanged < nThroughPoints) { + // A subset of points were altered from the requested location. + // Check to see if the relationship among them is preserved + + let changedInd1 = throughPointsChanged[0]; + let relationshipPreserved = true; + + if (nThroughPointsChanged > 1) { + let orig1 = numericalThroughPoints[changedInd1]; + let changed1 = resultingNumericalThroughPoints[changedInd1]; + let tol = 1e-6; + + let changevec1 = orig1.map((v, i) => v - changed1[i]); + + for (let ind of throughPointsChanged.slice(1)) { + let orig2 = numericalThroughPoints[ind]; + let changed2 = resultingNumericalThroughPoints[ind]; + let changevec2 = orig2.map((v, i) => v - changed2[i]); + + if ( + !changevec1.every((v, i) => Math.abs(v - changevec2[i]) < tol) + ) { + relationshipPreserved = false; + break; + } + } + } + + if (relationshipPreserved) { + let thetaOfChanged = throughAngles[changedInd1]; + + let newCenter = [ + resultingNumericalThroughPoints[changedInd1][0] - + radius * Math.cos(thetaOfChanged), + resultingNumericalThroughPoints[changedInd1][1] - + radius * Math.sin(thetaOfChanged), + ]; + + let newNumericalThroughPoints = []; + + for (let i = 0; i < nThroughPoints; i++) { + if (throughPointsChanged.includes(i)) { + newNumericalThroughPoints.push( + resultingNumericalThroughPoints[i], + ); + } else { + let theta = throughAngles[i]; + let pt = [ + newCenter[0] + radius * Math.cos(theta), + newCenter[1] + radius * Math.sin(theta), + ]; + newNumericalThroughPoints.push(pt); + } + } + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "numericalThroughPoints", + value: newNumericalThroughPoints, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } + } + + // if no modifications were made, still need to update renderers + // as original update was performed with skipping renderer update + return await this.coreFunctions.updateRenderers({ + actionId, + sourceInformation, + skipRendererUpdate, + }); } async circleClicked({ From 7fcd48cc105f9056cbbdf13b8f6f2aa006340674 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 24 Apr 2023 18:35:02 +0000 Subject: [PATCH 2/7] New constrained movement behavior for other components --- src/Core/components/Line.js | 81 +++++- src/Core/components/LineSegment.js | 82 +++++- src/Core/components/Polyline.js | 103 +++++++- src/Core/components/Ray.js | 88 ++++++- src/Core/components/Rectangle.js | 125 ++++++++- src/Core/components/RegularPolygon.js | 355 ++++++++++++++++++++++++++ src/Core/components/Triangle.js | 20 -- src/Core/components/Vector.js | 85 +++++- 8 files changed, 894 insertions(+), 45 deletions(-) diff --git a/src/Core/components/Line.js b/src/Core/components/Line.js index 5b6e023e20..37ea2a2c2e 100644 --- a/src/Core/components/Line.js +++ b/src/Core/components/Line.js @@ -1795,7 +1795,7 @@ export default class Line extends GraphicalComponent { } if (transient) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: [ { updateType: "updateValue", @@ -1807,10 +1807,10 @@ export default class Line extends GraphicalComponent { transient: true, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, }); } else { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: [ { updateType: "updateValue", @@ -1821,7 +1821,7 @@ export default class Line extends GraphicalComponent { ], actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -1834,6 +1834,79 @@ export default class Line extends GraphicalComponent { }, }); } + + if (!(await this.stateValues.basedOnSlope)) { + // based on two points + + let numericalPoints = [point1coords, point2coords]; + let resultingNumericalPoints = await this.stateValues.numericalPoints; + + let pointsChanged = []; + let nPointsChanged = 0; + + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } + } + + if (nPointsChanged === 1) { + // one point was altered from the requested location. + + let changedInd = pointsChanged[0]; + + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); + + let newNumericalPoints = []; + + for (let i = 0; i < 2; i++) { + if (i === changedInd) { + newNumericalPoints.push(resultingNumericalPoints[i]); + } else { + newNumericalPoints.push( + numericalPoints[i].map((v, j) => v - changevec1[j]), + ); + } + } + + let newPointComponents = {}; + for (let ind in newNumericalPoints) { + newPointComponents[ind + ",0"] = me.fromAst( + newNumericalPoints[ind][0], + ); + newPointComponents[ind + ",1"] = me.fromAst( + newNumericalPoints[ind][1], + ); + } + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "points", + value: newPointComponents, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } + + // if no modifications were made, still need to update renderers + // as original update was performed with skipping renderer update + return await this.coreFunctions.updateRenderers({ + actionId, + sourceInformation, + skipRendererUpdate, + }); } switchLine() {} diff --git a/src/Core/components/LineSegment.js b/src/Core/components/LineSegment.js index b609fbfb69..07d81c4580 100644 --- a/src/Core/components/LineSegment.js +++ b/src/Core/components/LineSegment.js @@ -697,7 +697,7 @@ export default class LineSegment extends GraphicalComponent { } if (transient) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: [ { componentName: this.componentName, @@ -710,10 +710,10 @@ export default class LineSegment extends GraphicalComponent { transient: true, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, }); } else { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: [ { componentName: this.componentName, @@ -725,7 +725,7 @@ export default class LineSegment extends GraphicalComponent { ], actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -739,6 +739,80 @@ export default class LineSegment extends GraphicalComponent { }, }); } + + // if dragged the whole line segment, + // address case where only one endpoint is constrained + // to make line segment just translate in this case + if (point1coords !== undefined && point2coords !== undefined) { + let numericalPoints = [point1coords, point2coords]; + let resultingNumericalPoints = await this.stateValues.numericalEndpoints; + + let pointsChanged = []; + let nPointsChanged = 0; + + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } + } + + if (nPointsChanged === 1) { + // one point was altered from the requested location. + + let changedInd = pointsChanged[0]; + + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); + + let newNumericalPoints = []; + + for (let i = 0; i < 2; i++) { + if (i === changedInd) { + newNumericalPoints.push(resultingNumericalPoints[i]); + } else { + newNumericalPoints.push( + numericalPoints[i].map((v, j) => v - changevec1[j]), + ); + } + } + + let newPointComponents = {}; + for (let ind in newNumericalPoints) { + newPointComponents[ind + ",0"] = me.fromAst( + newNumericalPoints[ind][0], + ); + newPointComponents[ind + ",1"] = me.fromAst( + newNumericalPoints[ind][1], + ); + } + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "endpoints", + value: newPointComponents, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } + + // if no modifications were made, still need to update renderers + // as original update was performed with skipping renderer update + return await this.coreFunctions.updateRenderers({ + actionId, + sourceInformation, + skipRendererUpdate, + }); } async lineSegmentClicked({ diff --git a/src/Core/components/Polyline.js b/src/Core/components/Polyline.js index 4598cc65c3..81655ad4ae 100644 --- a/src/Core/components/Polyline.js +++ b/src/Core/components/Polyline.js @@ -616,7 +616,7 @@ export default class Polyline extends GraphicalComponent { } if (transient) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: [ { updateType: "updateValue", @@ -629,10 +629,10 @@ export default class Polyline extends GraphicalComponent { transient, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, }); } else { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: [ { updateType: "updateValue", @@ -644,7 +644,7 @@ export default class Polyline extends GraphicalComponent { ], actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -657,6 +657,101 @@ export default class Polyline extends GraphicalComponent { }, }); } + + if (nVerticesMoved > 1) { + // whole polygon dragged + + let numericalVertices = pointCoords; + let resultingNumericalVertices = await this.stateValues.numericalVertices; + let nVertices = await this.stateValues.nVertices; + + let verticesChanged = []; + let nVerticesChanged = 0; + + for (let [ind, vrtx] of numericalVertices.entries()) { + if (!vrtx.every((v, i) => v === resultingNumericalVertices[ind][i])) { + verticesChanged.push(ind); + nVerticesChanged++; + } + } + + if (nVerticesChanged > 0 && nVerticesChanged < nVertices) { + // A subset of points were altered from the requested location. + // Check to see if the relationship among them is preserved + + let changedInd1 = verticesChanged[0]; + let relationshipPreserved = true; + + let orig1 = numericalVertices[changedInd1]; + let changed1 = resultingNumericalVertices[changedInd1]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); + + if (nVerticesChanged > 1) { + let tol = 1e-6; + + for (let ind of verticesChanged.slice(1)) { + let orig2 = numericalVertices[ind]; + let changed2 = resultingNumericalVertices[ind]; + let changevec2 = orig2.map((v, i) => v - changed2[i]); + + if ( + !changevec1.every((v, i) => Math.abs(v - changevec2[i]) < tol) + ) { + relationshipPreserved = false; + break; + } + } + } + + if (relationshipPreserved) { + let newNumericalVertices = []; + + for (let i = 0; i < nVertices; i++) { + if (verticesChanged.includes(i)) { + newNumericalVertices.push(resultingNumericalVertices[i]); + } else { + newNumericalVertices.push( + numericalVertices[i].map((v, j) => v - changevec1[j]), + ); + } + } + + let newVertexComponents = {}; + for (let ind in newNumericalVertices) { + newVertexComponents[ind + ",0"] = me.fromAst( + newNumericalVertices[ind][0], + ); + newVertexComponents[ind + ",1"] = me.fromAst( + newNumericalVertices[ind][1], + ); + } + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "vertices", + value: newVertexComponents, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } + } + + // if no modifications were made, still need to update renderers + // as original update was performed with skipping renderer update + return await this.coreFunctions.updateRenderers({ + actionId, + sourceInformation, + skipRendererUpdate, + }); } async finalizePolylinePosition({ diff --git a/src/Core/components/Ray.js b/src/Core/components/Ray.js index c0f7e417cb..1d136644ee 100644 --- a/src/Core/components/Ray.js +++ b/src/Core/components/Ray.js @@ -1557,20 +1557,20 @@ export default class Ray extends GraphicalComponent { } if (transient) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions, transient, skippable, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, }); } else { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -1584,6 +1584,86 @@ export default class Ray extends GraphicalComponent { }, }); } + + // if dragged the whole ray that is based on endpoint and through point, + // address case where only one point is constrained + // to make ray just translate in this case + + if ( + endpointcoords !== undefined && + throughcoords !== undefined && + (await this.stateValues.basedOnEndpoint) && + (await this.stateValues.basedOnThrough) + ) { + let numericalPoints = [endpointcoords, throughcoords]; + let resultingNumericalPoints = [ + await this.stateValues.numericalEndpoint, + await this.stateValues.numericalThroughpoint, + ]; + + let pointsChanged = []; + let nPointsChanged = 0; + + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } + } + + if (nPointsChanged === 1) { + // one point was altered from the requested location. + + let changedInd = pointsChanged[0]; + + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); + + let newNumericalPoints = []; + + for (let i = 0; i < 2; i++) { + if (i === changedInd) { + newNumericalPoints.push(resultingNumericalPoints[i]); + } else { + newNumericalPoints.push( + numericalPoints[i].map((v, j) => v - changevec1[j]), + ); + } + } + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "endpoint", + value: newNumericalPoints[0].map((x) => me.fromAst(x)), + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "through", + value: newNumericalPoints[1].map((x) => me.fromAst(x)), + }, + ]; + + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } + + // if no modifications were made, still need to update renderers + // as original update was performed with skipping renderer update + return await this.coreFunctions.updateRenderers({ + actionId, + sourceInformation, + skipRendererUpdate, + }); } async rayClicked({ diff --git a/src/Core/components/Rectangle.js b/src/Core/components/Rectangle.js index 9bc05d49b0..7eaf4bdfd4 100644 --- a/src/Core/components/Rectangle.js +++ b/src/Core/components/Rectangle.js @@ -1294,7 +1294,7 @@ export default class Rectangle extends Polygon { sourceDetails, }); - if (Object.keys(pointCoords).length === 1) { + if (nVerticesMoved === 1) { // When dragging a rectangle corner, add additional instructions // if they are needed to ensure the opposite corner doesn't move @@ -1373,19 +1373,19 @@ export default class Rectangle extends Polygon { // console.log("movePolygon updateInstructions", updateInstructions); if (transient) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions, transient, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, }); } else { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -1398,5 +1398,120 @@ export default class Rectangle extends Polygon { }, }); } + + // if dragged the whole rectangle that is based on two points (vertices and/or center), + // address case where only one point is constrained + // to make rectangle just translate in this case + + if (nVerticesMoved > 1) { + let nVerticesSpecified = await this.stateValues.nVerticesSpecified; + + if ( + nVerticesSpecified > 1 || + (nVerticesSpecified === 1 && + (await this.stateValues.haveSpecifiedCenter)) + ) { + let resultingNumericalVertices = await this.stateValues + .numericalVertices; + + let numericalPoints, resultingNumericalPoints; + + if (nVerticesSpecified > 1) { + // just look at first and third vertex + numericalPoints = [pointCoords[0], pointCoords[2]]; + resultingNumericalPoints = [ + resultingNumericalVertices[0], + resultingNumericalVertices[2], + ]; + } else { + // just look at center and first vertex + // (calculate center from first and third vertex) + + let numericalCenter = [ + (pointCoords[0][0] + pointCoords[2][0]) / 2, + (pointCoords[0][1] + pointCoords[2][1]) / 2, + ]; + + let resultingNumericalCenter = [ + (resultingNumericalVertices[0][0] + + resultingNumericalVertices[2][0]) / + 2, + (resultingNumericalVertices[0][1] + + resultingNumericalVertices[2][1]) / + 2, + ]; + + numericalPoints = [numericalCenter, pointCoords[0]]; + resultingNumericalPoints = [ + resultingNumericalCenter, + resultingNumericalVertices[0], + ]; + } + + let pointsChanged = []; + let nPointsChanged = 0; + + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } + } + + if (nPointsChanged === 1) { + // one point was altered from the requested location. + + let changedInd = pointsChanged[0]; + + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); + + let newNumericalVertices = []; + + for (let i = 0; i < 4; i++) { + newNumericalVertices.push( + pointCoords[i].map((v, j) => v - changevec1[j]), + ); + } + + let newVertexComponents = {}; + + for (let ind in newNumericalVertices) { + newVertexComponents[ind + ",0"] = me.fromAst( + newNumericalVertices[ind][0], + ); + newVertexComponents[ind + ",1"] = me.fromAst( + newNumericalVertices[ind][1], + ); + } + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "vertices", + value: newVertexComponents, + }, + ]; + + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } + } + + // if no modifications were made, still need to update renderers + // as original update was performed with skipping renderer update + return await this.coreFunctions.updateRenderers({ + actionId, + sourceInformation, + skipRendererUpdate, + }); } } diff --git a/src/Core/components/RegularPolygon.js b/src/Core/components/RegularPolygon.js index 59d6417e2f..bae83f9d0d 100644 --- a/src/Core/components/RegularPolygon.js +++ b/src/Core/components/RegularPolygon.js @@ -1742,4 +1742,359 @@ export default class RegularPolygon extends Polygon { return stateVariableDefinitions; } + + async movePolygon({ + pointCoords, + transient, + sourceDetails, + actionId, + sourceInformation = {}, + skipRendererUpdate = false, + }) { + let nVerticesMoved = Object.keys(pointCoords).length; + + if (nVerticesMoved === 1) { + // single vertex dragged + + if (!(await this.stateValues.verticesDraggable)) { + return await this.coreFunctions.resolveAction({ actionId }); + } + + // If a single vertex is dragged, we perform update on the vertex, + // as one typically does for a polygon + let vertexComponents = {}; + for (let ind in pointCoords) { + vertexComponents[ind + ",0"] = me.fromAst(pointCoords[ind][0]); + vertexComponents[ind + ",1"] = me.fromAst(pointCoords[ind][1]); + } + + if (transient) { + return await this.coreFunctions.performUpdate({ + updateInstructions: [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "vertices", + value: vertexComponents, + sourceDetails, + }, + ], + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } else { + return await this.coreFunctions.performUpdate({ + updateInstructions: [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "vertices", + value: vertexComponents, + sourceDetails, + }, + ], + actionId, + sourceInformation, + skipRendererUpdate, + event: { + verb: "interacted", + object: { + componentName: this.componentName, + componentType: this.componentType, + }, + result: { + pointCoordinates: pointCoords, + }, + }, + }); + } + } + + // whole polyline dragged + if (!(await this.stateValues.draggable)) { + return await this.coreFunctions.resolveAction({ actionId }); + } + + // In order to detect if one of the points used to defined the regular polygon is constrained + // (so that we can adjust to keep the shape in that case) + // we perform that calculations of the inverseDefinition of vertices here + // and perform that update directly on centerComponents and directionWithRadius. + // (The inverse definition of vertices will still be used if other components + // are connected to the vertices.) + // We just want this special behavior for the case when the entire polygon + // is moved by the renderer. + + let nVertices = await this.stateValues.nVertices; + + // First calculate the desired centers as the average of all points + + let center_x = 0, + center_y = 0; + + console.log({ nVertices, nVerticesMoved, pointCoords }); + + for (let vertexInd = 0; vertexInd < nVertices; vertexInd++) { + center_x += pointCoords[vertexInd][0]; + center_y += pointCoords[vertexInd][1]; + } + + center_x /= nVertices; + center_y /= nVertices; + + let center = [center_x, center_y]; + + // use the first index determine directionWithRadius + let vertex1 = pointCoords[0]; + + let directionWithRadius = [vertex1[0] - center[0], vertex1[1] - center[1]]; + + let updateInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "centerComponents", + value: center, + sourceDetails, + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "directionWithRadius", + value: directionWithRadius, + sourceDetails, + }, + ]; + + if (transient) { + await this.coreFunctions.performUpdate({ + updateInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate: true, + }); + } else { + await this.coreFunctions.performUpdate({ + updateInstructions, + actionId, + sourceInformation, + skipRendererUpdate: true, + event: { + verb: "interacted", + object: { + componentName: this.componentName, + componentType: this.componentType, + }, + result: { + pointCoordinates: pointCoords, + }, + }, + }); + } + + let nVerticesSpecified = await this.stateValues.nVerticesSpecified; + let haveSpecifiedCenter = await this.stateValues.haveSpecifiedCenter; + + if (haveSpecifiedCenter) { + if (nVerticesSpecified >= 1) { + // polygon was determined by center and 1 vertex + + console.log("center and vertex"); + + let resultingCenter = await this.stateValues.centerComponents; + + let resultingDirectionWithRadius = await this.stateValues + .directionWithRadius; + let resultingVertex1 = [ + resultingCenter[0] + resultingDirectionWithRadius[0], + resultingCenter[1] + resultingDirectionWithRadius[1], + ]; + + console.log({ center, vertex1, resultingCenter, resultingVertex1 }); + + let tol = 1e-6; + + let vertex1Changed = !vertex1.every( + (v, i) => Math.abs(v - resultingVertex1[i]) < tol, + ); + let centerChanged = !center.every( + (v, i) => Math.abs(v - resultingCenter[i]) < tol, + ); + + console.log({ vertex1Changed, centerChanged }); + + if (centerChanged) { + if (!vertex1Changed) { + // only center changed + // keep directionWithRadius the same + // and use new center position + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "centerComponents", + value: resultingCenter, + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "directionWithRadius", + value: directionWithRadius, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } else if (vertex1Changed) { + // only vertex 1 changed + // keep directionWithRadius the same + // adjust center to put vertex 1 at its new location + + let newCenter = [ + resultingVertex1[0] - directionWithRadius[0], + resultingVertex1[1] - directionWithRadius[1], + ]; + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "centerComponents", + value: newCenter, + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "directionWithRadius", + value: directionWithRadius, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } + } else if (nVerticesSpecified >= 2) { + //polygon was determined by two vertices + + // calculate the value of vertex2 calculated from center and vertex1 + let angle = (2 * Math.PI) / nVertices; + + let c = Math.cos(angle); + let s = Math.sin(angle); + + let directionWithRadius2 = [ + directionWithRadius[0] * c - directionWithRadius[1] * s, + directionWithRadius[0] * s + directionWithRadius[1] * c, + ]; + + let vertex2 = [ + directionWithRadius2[0] + center[0], + directionWithRadius2[1] + center[1], + ]; + + let resultingVertices = await this.stateValues.vertices; + let resultingVertex1 = resultingVertices[0].map((x) => + x.evaluate_to_constant(), + ); + let resultingVertex2 = resultingVertices[1].map((x) => + x.evaluate_to_constant(), + ); + + let tol = 1e-6; + + let vertex1Changed = !vertex1.every( + (v, i) => Math.abs(v - resultingVertex1[i]) < tol, + ); + let vertex2Changed = !vertex2.every( + (v, i) => Math.abs(v - resultingVertex2[i]) < tol, + ); + + if (vertex1Changed) { + if (!vertex2Changed) { + // only vertex 1 changed + // keep directionWithRadius the same + // adjust center to put vertex 1 at its new location + + let newCenter = [ + resultingVertex1[0] - directionWithRadius[0], + resultingVertex1[1] - directionWithRadius[1], + ]; + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "centerComponents", + value: newCenter, + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "directionWithRadius", + value: directionWithRadius, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } else if (vertex2Changed) { + // only vertex 2 changed + // keep directionWithRadius the same + // adjust center to put vertex 2 at its new location + + let newCenter = [ + resultingVertex2[0] - directionWithRadius2[0], + resultingVertex2[1] - directionWithRadius2[1], + ]; + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "centerComponents", + value: newCenter, + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "directionWithRadius", + value: directionWithRadius, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } + + // if no modifications were made, still need to update renderers + // as original update was performed with skipping renderer update + return await this.coreFunctions.updateRenderers({ + actionId, + sourceInformation, + skipRendererUpdate, + }); + } } diff --git a/src/Core/components/Triangle.js b/src/Core/components/Triangle.js index 0a8371cd62..91366bbd64 100644 --- a/src/Core/components/Triangle.js +++ b/src/Core/components/Triangle.js @@ -278,23 +278,3 @@ export default class Triangle extends Polygon { return stateVariableDefinitions; } } - -function mergeVertex({ workspaceVertices, currentVertexValue, desiredVertex }) { - // If have any empty values in desired value, - // merge with current values, or value from workspace - - let vertexAst; - if (workspaceVertices) { - // if have desired expresson from workspace, use that instead of currentValue - vertexAst = workspaceVertices.slice(0); - } else { - vertexAst = currentVertexValue.tree.slice(0); - } - for (let [ind, value] of desiredVertex.tree.entries()) { - if (value !== undefined) { - vertexAst[ind] = value; - } - } - - return me.fromAst(vertexAst); -} diff --git a/src/Core/components/Vector.js b/src/Core/components/Vector.js index 0022f2fae4..41cd85d291 100644 --- a/src/Core/components/Vector.js +++ b/src/Core/components/Vector.js @@ -2327,20 +2327,20 @@ export default class Vector extends GraphicalComponent { } if (transient) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions, transient, skippable, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, }); } else { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions, actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -2354,6 +2354,83 @@ export default class Vector extends GraphicalComponent { }, }); } + + // if dragged the whole ray that is based on endpoint and through point, + // address case where only one point is constrained + // to make ray just translate in this case + + if ( + tailcoords !== undefined && + headcoords !== undefined && + (await this.stateValues.basedOnTail) && + (await this.stateValues.basedOnHead) + ) { + let numericalPoints = [tailcoords, headcoords]; + let resultingNumericalPoints = await this.stateValues.numericalEndpoints; + + let pointsChanged = []; + let nPointsChanged = 0; + + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } + } + + if (nPointsChanged === 1) { + // one point was altered from the requested location. + + let changedInd = pointsChanged[0]; + + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); + + let newNumericalPoints = []; + + for (let i = 0; i < 2; i++) { + if (i === changedInd) { + newNumericalPoints.push(resultingNumericalPoints[i]); + } else { + newNumericalPoints.push( + numericalPoints[i].map((v, j) => v - changevec1[j]), + ); + } + } + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "tail", + value: newNumericalPoints[0].map((x) => me.fromAst(x)), + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "head", + value: newNumericalPoints[1].map((x) => me.fromAst(x)), + }, + ]; + + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } + + // if no modifications were made, still need to update renderers + // as original update was performed with skipping renderer update + return await this.coreFunctions.updateRenderers({ + actionId, + sourceInformation, + skipRendererUpdate, + }); } async vectorClicked({ From b0932b2945bd86c45fdab373fb3b185e15b236c0 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Mon, 24 Apr 2023 23:02:55 +0000 Subject: [PATCH 3/7] allowFlexibleMotion attribute restores old behavior --- src/Core/components/Circle.js | 269 ++++++++++++++------------ src/Core/components/Line.js | 115 ++++++----- src/Core/components/LineSegment.js | 125 +++++++----- src/Core/components/Polyline.js | 158 ++++++++------- src/Core/components/Ray.js | 139 +++++++------ src/Core/components/Rectangle.js | 200 ++++++++++--------- src/Core/components/RegularPolygon.js | 258 ++++++++++++------------ src/Core/components/Vector.js | 131 +++++++------ 8 files changed, 758 insertions(+), 637 deletions(-) diff --git a/src/Core/components/Circle.js b/src/Core/components/Circle.js index 12b0a28983..f2e11e3270 100644 --- a/src/Core/components/Circle.js +++ b/src/Core/components/Circle.js @@ -38,6 +38,13 @@ export default class Circle extends Curve { forRenderer: true, }; + attributes.allowFlexibleMotion = { + createComponentOfType: "boolean", + createStateVariable: "allowFlexibleMotion", + defaultValue: false, + public: true, + }; + delete attributes.parMin; delete attributes.parMax; delete attributes.variable; @@ -2571,7 +2578,8 @@ export default class Circle extends Curve { }); } - // move circle + // Note: we set skipRendererUpdate to true + // so that we can make further adjustments before the renderers are updated if (transient) { await this.coreFunctions.performUpdate({ updateInstructions: instructions, @@ -2599,139 +2607,38 @@ export default class Circle extends Curve { }); } - // if circle is based on more than 1 point (center or throughpoints) - // and a subset of those points appear to be constrained while preserving their relationship - // then move the other points in attempt to preserve their relationship with the constrained points, - // which will keep the radius of the circle fixed - - let resultingCenter = await this.stateValues.numericalCenter; - let resultingNumericalThroughPoints = await this.stateValues - .numericalThroughPoints; - - if (numericalPrescribedCenter.length > 0 && nThroughPoints === 1) { - // center and one through point - - let throughPointUnchanged = numericalThroughPoints[0].every( - (v, i) => v === resultingNumericalThroughPoints[0][i], - ); - - let centerUnchanged = center.every((v, i) => v === resultingCenter[i]); - - if (throughPointUnchanged && !centerUnchanged) { - let theta = throughAngles[0]; - let newNumericalThroughPoints = [ - [ - resultingCenter[0] + radius * Math.cos(theta), - resultingCenter[1] + radius * Math.sin(theta), - ], - ]; + // unless allowFlexibleMotion is set + // we attempt to keep the radius of the circle fixed + // even if one of the points defining it is constrained - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "numericalThroughPoints", - value: newNumericalThroughPoints, - }, - ]; - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); - } else if (centerUnchanged && !throughPointUnchanged) { - let theta = throughAngles[0]; - let newCenter = [ - resultingNumericalThroughPoints[0][0] - radius * Math.cos(theta), - resultingNumericalThroughPoints[0][1] - radius * Math.sin(theta), - ]; + if (!(await this.stateValues.allowFlexibleMotion)) { + // if circle is based on more than 1 point (center or throughpoints) + // and a subset of those points appear to be constrained while preserving their relationship + // then move the other points in attempt to preserve their relationship with the constrained points, + // which will keep the radius of the circle fixed - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "numericalCenter", - value: newCenter, - }, - ]; - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); - } - } else if (nThroughPoints >= 2) { - let throughPointsChanged = []; - let nThroughPointsChanged = 0; + let resultingCenter = await this.stateValues.numericalCenter; + let resultingNumericalThroughPoints = await this.stateValues + .numericalThroughPoints; - for (let [ind, pt] of numericalThroughPoints.entries()) { - if ( - !pt.every((v, i) => v === resultingNumericalThroughPoints[ind][i]) - ) { - throughPointsChanged.push(ind); - nThroughPointsChanged++; - } - } + if (numericalPrescribedCenter.length > 0 && nThroughPoints === 1) { + // center and one through point - if (nThroughPointsChanged > 0 && nThroughPointsChanged < nThroughPoints) { - // A subset of points were altered from the requested location. - // Check to see if the relationship among them is preserved + let throughPointUnchanged = numericalThroughPoints[0].every( + (v, i) => v === resultingNumericalThroughPoints[0][i], + ); - let changedInd1 = throughPointsChanged[0]; - let relationshipPreserved = true; + let centerUnchanged = center.every((v, i) => v === resultingCenter[i]); - if (nThroughPointsChanged > 1) { - let orig1 = numericalThroughPoints[changedInd1]; - let changed1 = resultingNumericalThroughPoints[changedInd1]; - let tol = 1e-6; - - let changevec1 = orig1.map((v, i) => v - changed1[i]); - - for (let ind of throughPointsChanged.slice(1)) { - let orig2 = numericalThroughPoints[ind]; - let changed2 = resultingNumericalThroughPoints[ind]; - let changevec2 = orig2.map((v, i) => v - changed2[i]); - - if ( - !changevec1.every((v, i) => Math.abs(v - changevec2[i]) < tol) - ) { - relationshipPreserved = false; - break; - } - } - } - - if (relationshipPreserved) { - let thetaOfChanged = throughAngles[changedInd1]; - - let newCenter = [ - resultingNumericalThroughPoints[changedInd1][0] - - radius * Math.cos(thetaOfChanged), - resultingNumericalThroughPoints[changedInd1][1] - - radius * Math.sin(thetaOfChanged), + if (throughPointUnchanged && !centerUnchanged) { + let theta = throughAngles[0]; + let newNumericalThroughPoints = [ + [ + resultingCenter[0] + radius * Math.cos(theta), + resultingCenter[1] + radius * Math.sin(theta), + ], ]; - let newNumericalThroughPoints = []; - - for (let i = 0; i < nThroughPoints; i++) { - if (throughPointsChanged.includes(i)) { - newNumericalThroughPoints.push( - resultingNumericalThroughPoints[i], - ); - } else { - let theta = throughAngles[i]; - let pt = [ - newCenter[0] + radius * Math.cos(theta), - newCenter[1] + radius * Math.sin(theta), - ]; - newNumericalThroughPoints.push(pt); - } - } - let newInstructions = [ { updateType: "updateValue", @@ -2747,6 +2654,116 @@ export default class Circle extends Curve { sourceInformation, skipRendererUpdate, }); + } else if (centerUnchanged && !throughPointUnchanged) { + let theta = throughAngles[0]; + let newCenter = [ + resultingNumericalThroughPoints[0][0] - radius * Math.cos(theta), + resultingNumericalThroughPoints[0][1] - radius * Math.sin(theta), + ]; + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "numericalCenter", + value: newCenter, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } else if (nThroughPoints >= 2) { + let throughPointsChanged = []; + let nThroughPointsChanged = 0; + + for (let [ind, pt] of numericalThroughPoints.entries()) { + if ( + !pt.every((v, i) => v === resultingNumericalThroughPoints[ind][i]) + ) { + throughPointsChanged.push(ind); + nThroughPointsChanged++; + } + } + + if ( + nThroughPointsChanged > 0 && + nThroughPointsChanged < nThroughPoints + ) { + // A subset of points were altered from the requested location. + // Check to see if the relationship among them is preserved + + let changedInd1 = throughPointsChanged[0]; + let relationshipPreserved = true; + + if (nThroughPointsChanged > 1) { + let orig1 = numericalThroughPoints[changedInd1]; + let changed1 = resultingNumericalThroughPoints[changedInd1]; + let tol = 1e-6; + + let changevec1 = orig1.map((v, i) => v - changed1[i]); + + for (let ind of throughPointsChanged.slice(1)) { + let orig2 = numericalThroughPoints[ind]; + let changed2 = resultingNumericalThroughPoints[ind]; + let changevec2 = orig2.map((v, i) => v - changed2[i]); + + if ( + !changevec1.every((v, i) => Math.abs(v - changevec2[i]) < tol) + ) { + relationshipPreserved = false; + break; + } + } + } + + if (relationshipPreserved) { + let thetaOfChanged = throughAngles[changedInd1]; + + let newCenter = [ + resultingNumericalThroughPoints[changedInd1][0] - + radius * Math.cos(thetaOfChanged), + resultingNumericalThroughPoints[changedInd1][1] - + radius * Math.sin(thetaOfChanged), + ]; + + let newNumericalThroughPoints = []; + + for (let i = 0; i < nThroughPoints; i++) { + if (throughPointsChanged.includes(i)) { + newNumericalThroughPoints.push( + resultingNumericalThroughPoints[i], + ); + } else { + let theta = throughAngles[i]; + let pt = [ + newCenter[0] + radius * Math.cos(theta), + newCenter[1] + radius * Math.sin(theta), + ]; + newNumericalThroughPoints.push(pt); + } + } + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "numericalThroughPoints", + value: newNumericalThroughPoints, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } } } } diff --git a/src/Core/components/Line.js b/src/Core/components/Line.js index 37ea2a2c2e..702f45606c 100644 --- a/src/Core/components/Line.js +++ b/src/Core/components/Line.js @@ -81,6 +81,13 @@ export default class Line extends GraphicalComponent { validValues: ["upperright", "upperleft", "lowerright", "lowerleft"], }; + attributes.allowFlexibleMotion = { + createComponentOfType: "boolean", + createStateVariable: "allowFlexibleMotion", + defaultValue: false, + public: true, + }; + return attributes; } @@ -1794,6 +1801,8 @@ export default class Line extends GraphicalComponent { desiredPoints["1,1"] = me.fromAst(point2coords[1]); } + // Note: we set skipRendererUpdate to true + // so that we can make further adjustments before the renderers are updated if (transient) { await this.coreFunctions.performUpdate({ updateInstructions: [ @@ -1835,68 +1844,76 @@ export default class Line extends GraphicalComponent { }); } - if (!(await this.stateValues.basedOnSlope)) { - // based on two points + // unless allowFlexibleMotion is set, + // we will attempt to keep the slope of the line fixed + // even if one of the points is constrained + if (!(await this.stateValues.allowFlexibleMotion)) { + if (!(await this.stateValues.basedOnSlope)) { + // based on two points - let numericalPoints = [point1coords, point2coords]; - let resultingNumericalPoints = await this.stateValues.numericalPoints; + let numericalPoints = [point1coords, point2coords]; + let resultingNumericalPoints = await this.stateValues.numericalPoints; - let pointsChanged = []; - let nPointsChanged = 0; + let pointsChanged = []; + let nPointsChanged = 0; - for (let [ind, pt] of numericalPoints.entries()) { - if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { - pointsChanged.push(ind); - nPointsChanged++; + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } } - } - if (nPointsChanged === 1) { - // one point was altered from the requested location. + if (nPointsChanged === 1) { + // One point was altered from the requested location + // while the other point stayed at the requested location. + // We interpret this as one point being constrained and the second one being free + // and we move the second point to keep their relative position fixed. - let changedInd = pointsChanged[0]; + let changedInd = pointsChanged[0]; - let orig1 = numericalPoints[changedInd]; - let changed1 = resultingNumericalPoints[changedInd]; - let changevec1 = orig1.map((v, i) => v - changed1[i]); + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); - let newNumericalPoints = []; + let newNumericalPoints = []; - for (let i = 0; i < 2; i++) { - if (i === changedInd) { - newNumericalPoints.push(resultingNumericalPoints[i]); - } else { - newNumericalPoints.push( - numericalPoints[i].map((v, j) => v - changevec1[j]), + for (let i = 0; i < 2; i++) { + if (i === changedInd) { + newNumericalPoints.push(resultingNumericalPoints[i]); + } else { + newNumericalPoints.push( + numericalPoints[i].map((v, j) => v - changevec1[j]), + ); + } + } + + let newPointComponents = {}; + for (let ind in newNumericalPoints) { + newPointComponents[ind + ",0"] = me.fromAst( + newNumericalPoints[ind][0], + ); + newPointComponents[ind + ",1"] = me.fromAst( + newNumericalPoints[ind][1], ); } - } - let newPointComponents = {}; - for (let ind in newNumericalPoints) { - newPointComponents[ind + ",0"] = me.fromAst( - newNumericalPoints[ind][0], - ); - newPointComponents[ind + ",1"] = me.fromAst( - newNumericalPoints[ind][1], - ); + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "points", + value: newPointComponents, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); } - - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "points", - value: newPointComponents, - }, - ]; - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); } } diff --git a/src/Core/components/LineSegment.js b/src/Core/components/LineSegment.js index 07d81c4580..190739ca62 100644 --- a/src/Core/components/LineSegment.js +++ b/src/Core/components/LineSegment.js @@ -51,6 +51,13 @@ export default class LineSegment extends GraphicalComponent { validValues: ["upperright", "upperleft", "lowerright", "lowerleft"], }; + attributes.allowFlexibleMotion = { + createComponentOfType: "boolean", + createStateVariable: "allowFlexibleMotion", + defaultValue: false, + public: true, + }; + return attributes; } @@ -696,6 +703,8 @@ export default class LineSegment extends GraphicalComponent { newComponents["1,1"] = me.fromAst(point2coords[1]); } + // Note: we set skipRendererUpdate to true + // so that we can make further adjustments before the renderers are updated if (transient) { await this.coreFunctions.performUpdate({ updateInstructions: [ @@ -740,69 +749,79 @@ export default class LineSegment extends GraphicalComponent { }); } - // if dragged the whole line segment, - // address case where only one endpoint is constrained - // to make line segment just translate in this case - if (point1coords !== undefined && point2coords !== undefined) { - let numericalPoints = [point1coords, point2coords]; - let resultingNumericalPoints = await this.stateValues.numericalEndpoints; - - let pointsChanged = []; - let nPointsChanged = 0; - - for (let [ind, pt] of numericalPoints.entries()) { - if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { - pointsChanged.push(ind); - nPointsChanged++; + // Unless allowFlexibleMotion is set + // we will attempt to keep the relationship between the two endpoints fixed + // when the whole line segment is moved, + // even if one of the endpoints is constrained. + if (!(await this.stateValues.allowFlexibleMotion)) { + // if dragged the whole line segment, + // address case where only one endpoint is constrained + // to make line segment just translate in this case + if (point1coords !== undefined && point2coords !== undefined) { + let numericalPoints = [point1coords, point2coords]; + let resultingNumericalPoints = await this.stateValues + .numericalEndpoints; + + let pointsChanged = []; + let nPointsChanged = 0; + + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } } - } - if (nPointsChanged === 1) { - // one point was altered from the requested location. + if (nPointsChanged === 1) { + // One endpoint was altered from the requested location + // while the other endpoint stayed at the requested location. + // We interpret this as one endpoint being constrained and the second one being free + // and we move the second endpoint to keep their relative position fixed. - let changedInd = pointsChanged[0]; + let changedInd = pointsChanged[0]; - let orig1 = numericalPoints[changedInd]; - let changed1 = resultingNumericalPoints[changedInd]; - let changevec1 = orig1.map((v, i) => v - changed1[i]); + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); - let newNumericalPoints = []; + let newNumericalPoints = []; - for (let i = 0; i < 2; i++) { - if (i === changedInd) { - newNumericalPoints.push(resultingNumericalPoints[i]); - } else { - newNumericalPoints.push( - numericalPoints[i].map((v, j) => v - changevec1[j]), + for (let i = 0; i < 2; i++) { + if (i === changedInd) { + newNumericalPoints.push(resultingNumericalPoints[i]); + } else { + newNumericalPoints.push( + numericalPoints[i].map((v, j) => v - changevec1[j]), + ); + } + } + + let newPointComponents = {}; + for (let ind in newNumericalPoints) { + newPointComponents[ind + ",0"] = me.fromAst( + newNumericalPoints[ind][0], + ); + newPointComponents[ind + ",1"] = me.fromAst( + newNumericalPoints[ind][1], ); } - } - let newPointComponents = {}; - for (let ind in newNumericalPoints) { - newPointComponents[ind + ",0"] = me.fromAst( - newNumericalPoints[ind][0], - ); - newPointComponents[ind + ",1"] = me.fromAst( - newNumericalPoints[ind][1], - ); + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "endpoints", + value: newPointComponents, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); } - - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "endpoints", - value: newPointComponents, - }, - ]; - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); } } diff --git a/src/Core/components/Polyline.js b/src/Core/components/Polyline.js index 81655ad4ae..3085343659 100644 --- a/src/Core/components/Polyline.js +++ b/src/Core/components/Polyline.js @@ -33,6 +33,13 @@ export default class Polyline extends GraphicalComponent { createComponentOfType: "_pointListComponent", }; + attributes.allowFlexibleMotion = { + createComponentOfType: "boolean", + createStateVariable: "allowFlexibleMotion", + defaultValue: false, + public: true, + }; + return attributes; } @@ -615,6 +622,8 @@ export default class Polyline extends GraphicalComponent { vertexComponents[ind + ",1"] = me.fromAst(pointCoords[ind][1]); } + // Note: we set skipRendererUpdate to true + // so that we can make further adjustments before the renderers are updated if (transient) { await this.coreFunctions.performUpdate({ updateInstructions: [ @@ -658,89 +667,102 @@ export default class Polyline extends GraphicalComponent { }); } - if (nVerticesMoved > 1) { - // whole polygon dragged - - let numericalVertices = pointCoords; - let resultingNumericalVertices = await this.stateValues.numericalVertices; - let nVertices = await this.stateValues.nVertices; - - let verticesChanged = []; - let nVerticesChanged = 0; - - for (let [ind, vrtx] of numericalVertices.entries()) { - if (!vrtx.every((v, i) => v === resultingNumericalVertices[ind][i])) { - verticesChanged.push(ind); - nVerticesChanged++; + // unless allowFlexibleMotion is set, + // we will attempt to preserve the relationship among all the vertices + // so that we have a rigid translation + // when the whole polyline is moved. + // This procedure may preserve the rigid translation + // even if a subset of the vertices are constrained. + if (!(await this.stateValues.allowFlexibleMotion)) { + if (nVerticesMoved > 1) { + // whole polyline dragged + + let numericalVertices = pointCoords; + let resultingNumericalVertices = await this.stateValues + .numericalVertices; + let nVertices = await this.stateValues.nVertices; + + let verticesChanged = []; + let nVerticesChanged = 0; + + for (let [ind, vrtx] of numericalVertices.entries()) { + if (!vrtx.every((v, i) => v === resultingNumericalVertices[ind][i])) { + verticesChanged.push(ind); + nVerticesChanged++; + } } - } - if (nVerticesChanged > 0 && nVerticesChanged < nVertices) { - // A subset of points were altered from the requested location. - // Check to see if the relationship among them is preserved + if (nVerticesChanged > 0 && nVerticesChanged < nVertices) { + // A subset of points were altered from the requested location. + // Check to see if the relationship among them is preserved - let changedInd1 = verticesChanged[0]; - let relationshipPreserved = true; + let changedInd1 = verticesChanged[0]; + let relationshipPreserved = true; - let orig1 = numericalVertices[changedInd1]; - let changed1 = resultingNumericalVertices[changedInd1]; - let changevec1 = orig1.map((v, i) => v - changed1[i]); + let orig1 = numericalVertices[changedInd1]; + let changed1 = resultingNumericalVertices[changedInd1]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); - if (nVerticesChanged > 1) { - let tol = 1e-6; + if (nVerticesChanged > 1) { + let tol = 1e-6; - for (let ind of verticesChanged.slice(1)) { - let orig2 = numericalVertices[ind]; - let changed2 = resultingNumericalVertices[ind]; - let changevec2 = orig2.map((v, i) => v - changed2[i]); + for (let ind of verticesChanged.slice(1)) { + let orig2 = numericalVertices[ind]; + let changed2 = resultingNumericalVertices[ind]; + let changevec2 = orig2.map((v, i) => v - changed2[i]); - if ( - !changevec1.every((v, i) => Math.abs(v - changevec2[i]) < tol) - ) { - relationshipPreserved = false; - break; + if ( + !changevec1.every((v, i) => Math.abs(v - changevec2[i]) < tol) + ) { + relationshipPreserved = false; + break; + } } } - } - if (relationshipPreserved) { - let newNumericalVertices = []; + if (relationshipPreserved) { + // All the vertices that were altered from their requested location + // were altered in a way consistent with a rigid translation. + // Attempt to move the remaining vertices to achieve a rigid translation + // of the whole polyline. + let newNumericalVertices = []; - for (let i = 0; i < nVertices; i++) { - if (verticesChanged.includes(i)) { - newNumericalVertices.push(resultingNumericalVertices[i]); - } else { - newNumericalVertices.push( - numericalVertices[i].map((v, j) => v - changevec1[j]), + for (let i = 0; i < nVertices; i++) { + if (verticesChanged.includes(i)) { + newNumericalVertices.push(resultingNumericalVertices[i]); + } else { + newNumericalVertices.push( + numericalVertices[i].map((v, j) => v - changevec1[j]), + ); + } + } + + let newVertexComponents = {}; + for (let ind in newNumericalVertices) { + newVertexComponents[ind + ",0"] = me.fromAst( + newNumericalVertices[ind][0], + ); + newVertexComponents[ind + ",1"] = me.fromAst( + newNumericalVertices[ind][1], ); } - } - let newVertexComponents = {}; - for (let ind in newNumericalVertices) { - newVertexComponents[ind + ",0"] = me.fromAst( - newNumericalVertices[ind][0], - ); - newVertexComponents[ind + ",1"] = me.fromAst( - newNumericalVertices[ind][1], - ); + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "vertices", + value: newVertexComponents, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); } - - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "vertices", - value: newVertexComponents, - }, - ]; - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); } } } diff --git a/src/Core/components/Ray.js b/src/Core/components/Ray.js index 1d136644ee..e446177612 100644 --- a/src/Core/components/Ray.js +++ b/src/Core/components/Ray.js @@ -35,6 +35,13 @@ export default class Ray extends GraphicalComponent { createComponentOfType: "vector", }; + attributes.allowFlexibleMotion = { + createComponentOfType: "boolean", + createStateVariable: "allowFlexibleMotion", + defaultValue: false, + public: true, + }; + return attributes; } @@ -1556,6 +1563,8 @@ export default class Ray extends GraphicalComponent { } } + // Note: we set skipRendererUpdate to true + // so that we can make further adjustments before the renderers are updated if (transient) { await this.coreFunctions.performUpdate({ updateInstructions, @@ -1585,75 +1594,83 @@ export default class Ray extends GraphicalComponent { }); } - // if dragged the whole ray that is based on endpoint and through point, - // address case where only one point is constrained - // to make ray just translate in this case - - if ( - endpointcoords !== undefined && - throughcoords !== undefined && - (await this.stateValues.basedOnEndpoint) && - (await this.stateValues.basedOnThrough) - ) { - let numericalPoints = [endpointcoords, throughcoords]; - let resultingNumericalPoints = [ - await this.stateValues.numericalEndpoint, - await this.stateValues.numericalThroughpoint, - ]; - - let pointsChanged = []; - let nPointsChanged = 0; - - for (let [ind, pt] of numericalPoints.entries()) { - if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { - pointsChanged.push(ind); - nPointsChanged++; + // unless allowFlexibleMotion is set, + // we will attempt to keep the slope of the ray fixed + // even if one of the points is constrained + if (!(await this.stateValues.allowFlexibleMotion)) { + // if dragged the whole ray that is based on endpoint and through point, + // address case where only one point is constrained + // to make ray just translate in this case + + if ( + endpointcoords !== undefined && + throughcoords !== undefined && + (await this.stateValues.basedOnEndpoint) && + (await this.stateValues.basedOnThrough) + ) { + let numericalPoints = [endpointcoords, throughcoords]; + let resultingNumericalPoints = [ + await this.stateValues.numericalEndpoint, + await this.stateValues.numericalThroughpoint, + ]; + + let pointsChanged = []; + let nPointsChanged = 0; + + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } } - } - if (nPointsChanged === 1) { - // one point was altered from the requested location. + if (nPointsChanged === 1) { + // One point was altered from the requested location + // while the other point stayed at the requested location. + // We interpret this as one point being constrained and the second one being free + // and we move the second point to keep their relative position fixed. - let changedInd = pointsChanged[0]; + let changedInd = pointsChanged[0]; - let orig1 = numericalPoints[changedInd]; - let changed1 = resultingNumericalPoints[changedInd]; - let changevec1 = orig1.map((v, i) => v - changed1[i]); + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); - let newNumericalPoints = []; + let newNumericalPoints = []; - for (let i = 0; i < 2; i++) { - if (i === changedInd) { - newNumericalPoints.push(resultingNumericalPoints[i]); - } else { - newNumericalPoints.push( - numericalPoints[i].map((v, j) => v - changevec1[j]), - ); + for (let i = 0; i < 2; i++) { + if (i === changedInd) { + newNumericalPoints.push(resultingNumericalPoints[i]); + } else { + newNumericalPoints.push( + numericalPoints[i].map((v, j) => v - changevec1[j]), + ); + } } - } - - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "endpoint", - value: newNumericalPoints[0].map((x) => me.fromAst(x)), - }, - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "through", - value: newNumericalPoints[1].map((x) => me.fromAst(x)), - }, - ]; - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "endpoint", + value: newNumericalPoints[0].map((x) => me.fromAst(x)), + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "through", + value: newNumericalPoints[1].map((x) => me.fromAst(x)), + }, + ]; + + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } } } diff --git a/src/Core/components/Rectangle.js b/src/Core/components/Rectangle.js index 7eaf4bdfd4..f7c14f6071 100644 --- a/src/Core/components/Rectangle.js +++ b/src/Core/components/Rectangle.js @@ -1370,8 +1370,8 @@ export default class Rectangle extends Polygon { } } - // console.log("movePolygon updateInstructions", updateInstructions); - + // Note: we set skipRendererUpdate to true + // so that we can make further adjustments before the renderers are updated if (transient) { await this.coreFunctions.performUpdate({ updateInstructions, @@ -1399,109 +1399,121 @@ export default class Rectangle extends Polygon { }); } - // if dragged the whole rectangle that is based on two points (vertices and/or center), - // address case where only one point is constrained - // to make rectangle just translate in this case - - if (nVerticesMoved > 1) { - let nVerticesSpecified = await this.stateValues.nVerticesSpecified; - - if ( - nVerticesSpecified > 1 || - (nVerticesSpecified === 1 && - (await this.stateValues.haveSpecifiedCenter)) - ) { - let resultingNumericalVertices = await this.stateValues - .numericalVertices; - - let numericalPoints, resultingNumericalPoints; - - if (nVerticesSpecified > 1) { - // just look at first and third vertex - numericalPoints = [pointCoords[0], pointCoords[2]]; - resultingNumericalPoints = [ - resultingNumericalVertices[0], - resultingNumericalVertices[2], - ]; - } else { - // just look at center and first vertex - // (calculate center from first and third vertex) - - let numericalCenter = [ - (pointCoords[0][0] + pointCoords[2][0]) / 2, - (pointCoords[0][1] + pointCoords[2][1]) / 2, - ]; - - let resultingNumericalCenter = [ - (resultingNumericalVertices[0][0] + - resultingNumericalVertices[2][0]) / - 2, - (resultingNumericalVertices[0][1] + - resultingNumericalVertices[2][1]) / - 2, - ]; - - numericalPoints = [numericalCenter, pointCoords[0]]; - resultingNumericalPoints = [ - resultingNumericalCenter, - resultingNumericalVertices[0], - ]; - } + // unless allowFlexibleMotion is set, + // we will attempt to preserve the relationship among all the vertices + // so that we have a rigid translation + // when the whole rectangle is moved. + // This procedure may preserve the rigid translation + // even if a subset of the vertices are constrained. + if (!(await this.stateValues.allowFlexibleMotion)) { + // if dragged the whole rectangle that is based on two points (vertices and/or center), + // address case where only one point is constrained + // to make rectangle just translate in this case + + if (nVerticesMoved > 1) { + let nVerticesSpecified = await this.stateValues.nVerticesSpecified; + + if ( + nVerticesSpecified > 1 || + (nVerticesSpecified === 1 && + (await this.stateValues.haveSpecifiedCenter)) + ) { + let resultingNumericalVertices = await this.stateValues + .numericalVertices; - let pointsChanged = []; - let nPointsChanged = 0; + let numericalPoints, resultingNumericalPoints; - for (let [ind, pt] of numericalPoints.entries()) { - if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { - pointsChanged.push(ind); - nPointsChanged++; - } - } + if (nVerticesSpecified > 1) { + // just look at first and third vertex + numericalPoints = [pointCoords[0], pointCoords[2]]; + resultingNumericalPoints = [ + resultingNumericalVertices[0], + resultingNumericalVertices[2], + ]; + } else { + // just look at center and first vertex + // (calculate center from first and third vertex) - if (nPointsChanged === 1) { - // one point was altered from the requested location. + let numericalCenter = [ + (pointCoords[0][0] + pointCoords[2][0]) / 2, + (pointCoords[0][1] + pointCoords[2][1]) / 2, + ]; - let changedInd = pointsChanged[0]; + let resultingNumericalCenter = [ + (resultingNumericalVertices[0][0] + + resultingNumericalVertices[2][0]) / + 2, + (resultingNumericalVertices[0][1] + + resultingNumericalVertices[2][1]) / + 2, + ]; - let orig1 = numericalPoints[changedInd]; - let changed1 = resultingNumericalPoints[changedInd]; - let changevec1 = orig1.map((v, i) => v - changed1[i]); + numericalPoints = [numericalCenter, pointCoords[0]]; + resultingNumericalPoints = [ + resultingNumericalCenter, + resultingNumericalVertices[0], + ]; + } - let newNumericalVertices = []; + let pointsChanged = []; + let nPointsChanged = 0; - for (let i = 0; i < 4; i++) { - newNumericalVertices.push( - pointCoords[i].map((v, j) => v - changevec1[j]), - ); + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } } - let newVertexComponents = {}; + if (nPointsChanged === 1) { + // One of the defining points (center or vertex) + // was altered from the requested location + // while the other point stayed at the requested location. + // We interpret this as one point being constrained and the second one being free + // and we move the second point to keep their relative position fixed. - for (let ind in newNumericalVertices) { - newVertexComponents[ind + ",0"] = me.fromAst( - newNumericalVertices[ind][0], - ); - newVertexComponents[ind + ",1"] = me.fromAst( - newNumericalVertices[ind][1], - ); - } + let changedInd = pointsChanged[0]; - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "vertices", - value: newVertexComponents, - }, - ]; - - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); + + let newNumericalVertices = []; + + for (let i = 0; i < 4; i++) { + newNumericalVertices.push( + pointCoords[i].map((v, j) => v - changevec1[j]), + ); + } + + let newVertexComponents = {}; + + for (let ind in newNumericalVertices) { + newVertexComponents[ind + ",0"] = me.fromAst( + newNumericalVertices[ind][0], + ); + newVertexComponents[ind + ",1"] = me.fromAst( + newNumericalVertices[ind][1], + ); + } + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "vertices", + value: newVertexComponents, + }, + ]; + + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } } } } diff --git a/src/Core/components/RegularPolygon.js b/src/Core/components/RegularPolygon.js index bae83f9d0d..13966b6df2 100644 --- a/src/Core/components/RegularPolygon.js +++ b/src/Core/components/RegularPolygon.js @@ -1760,6 +1760,9 @@ export default class RegularPolygon extends Polygon { return await this.coreFunctions.resolveAction({ actionId }); } + // Since the case where drag the entire regular polygon is complicated, + // we perform the entire move for a single vertex here and return. + // If a single vertex is dragged, we perform update on the vertex, // as one typically does for a polygon let vertexComponents = {}; @@ -1833,8 +1836,6 @@ export default class RegularPolygon extends Polygon { let center_x = 0, center_y = 0; - console.log({ nVertices, nVerticesMoved, pointCoords }); - for (let vertexInd = 0; vertexInd < nVertices; vertexInd++) { center_x += pointCoords[vertexInd][0]; center_y += pointCoords[vertexInd][1]; @@ -1867,6 +1868,8 @@ export default class RegularPolygon extends Polygon { }, ]; + // Note: we set skipRendererUpdate to true + // so that we can make further adjustments before the renderers are updated if (transient) { await this.coreFunctions.performUpdate({ updateInstructions, @@ -1894,49 +1897,79 @@ export default class RegularPolygon extends Polygon { }); } - let nVerticesSpecified = await this.stateValues.nVerticesSpecified; - let haveSpecifiedCenter = await this.stateValues.haveSpecifiedCenter; + // unless allowFlexibleMotion is set, + // we will attempt to create a rigid translation of the polygon + // even if a subset of the vertices are constrained. + if (!(await this.stateValues.allowFlexibleMotion)) { + let nVerticesSpecified = await this.stateValues.nVerticesSpecified; + let haveSpecifiedCenter = await this.stateValues.haveSpecifiedCenter; - if (haveSpecifiedCenter) { - if (nVerticesSpecified >= 1) { - // polygon was determined by center and 1 vertex + if (haveSpecifiedCenter) { + if (nVerticesSpecified >= 1) { + // polygon was determined by center and 1 vertex - console.log("center and vertex"); + let resultingCenter = await this.stateValues.centerComponents; - let resultingCenter = await this.stateValues.centerComponents; - - let resultingDirectionWithRadius = await this.stateValues - .directionWithRadius; - let resultingVertex1 = [ - resultingCenter[0] + resultingDirectionWithRadius[0], - resultingCenter[1] + resultingDirectionWithRadius[1], - ]; - - console.log({ center, vertex1, resultingCenter, resultingVertex1 }); - - let tol = 1e-6; + let resultingDirectionWithRadius = await this.stateValues + .directionWithRadius; + let resultingVertex1 = [ + resultingCenter[0] + resultingDirectionWithRadius[0], + resultingCenter[1] + resultingDirectionWithRadius[1], + ]; - let vertex1Changed = !vertex1.every( - (v, i) => Math.abs(v - resultingVertex1[i]) < tol, - ); - let centerChanged = !center.every( - (v, i) => Math.abs(v - resultingCenter[i]) < tol, - ); + let tol = 1e-6; - console.log({ vertex1Changed, centerChanged }); + let vertex1Changed = !vertex1.every( + (v, i) => Math.abs(v - resultingVertex1[i]) < tol, + ); + let centerChanged = !center.every( + (v, i) => Math.abs(v - resultingCenter[i]) < tol, + ); - if (centerChanged) { - if (!vertex1Changed) { - // only center changed + if (centerChanged) { + if (!vertex1Changed) { + // only center changed + // keep directionWithRadius the same + // and use new center position + + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "centerComponents", + value: resultingCenter, + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "directionWithRadius", + value: directionWithRadius, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } else if (vertex1Changed) { + // only vertex 1 changed // keep directionWithRadius the same - // and use new center position + // adjust center to put vertex 1 at its new location + + let newCenter = [ + resultingVertex1[0] - directionWithRadius[0], + resultingVertex1[1] - directionWithRadius[1], + ]; let newInstructions = [ { updateType: "updateValue", componentName: this.componentName, stateVariable: "centerComponents", - value: resultingCenter, + value: newCenter, }, { updateType: "updateValue", @@ -1953,84 +1986,84 @@ export default class RegularPolygon extends Polygon { skipRendererUpdate, }); } - } else if (vertex1Changed) { - // only vertex 1 changed - // keep directionWithRadius the same - // adjust center to put vertex 1 at its new location + } + } else if (nVerticesSpecified >= 2) { + //polygon was determined by two vertices - let newCenter = [ - resultingVertex1[0] - directionWithRadius[0], - resultingVertex1[1] - directionWithRadius[1], - ]; + // calculate the value of vertex2 calculated from center and vertex1 + let angle = (2 * Math.PI) / nVertices; - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "centerComponents", - value: newCenter, - }, - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "directionWithRadius", - value: directionWithRadius, - }, - ]; - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); - } - } - } else if (nVerticesSpecified >= 2) { - //polygon was determined by two vertices + let c = Math.cos(angle); + let s = Math.sin(angle); - // calculate the value of vertex2 calculated from center and vertex1 - let angle = (2 * Math.PI) / nVertices; + let directionWithRadius2 = [ + directionWithRadius[0] * c - directionWithRadius[1] * s, + directionWithRadius[0] * s + directionWithRadius[1] * c, + ]; - let c = Math.cos(angle); - let s = Math.sin(angle); + let vertex2 = [ + directionWithRadius2[0] + center[0], + directionWithRadius2[1] + center[1], + ]; - let directionWithRadius2 = [ - directionWithRadius[0] * c - directionWithRadius[1] * s, - directionWithRadius[0] * s + directionWithRadius[1] * c, - ]; + let resultingVertices = await this.stateValues.vertices; + let resultingVertex1 = resultingVertices[0].map((x) => + x.evaluate_to_constant(), + ); + let resultingVertex2 = resultingVertices[1].map((x) => + x.evaluate_to_constant(), + ); - let vertex2 = [ - directionWithRadius2[0] + center[0], - directionWithRadius2[1] + center[1], - ]; + let tol = 1e-6; - let resultingVertices = await this.stateValues.vertices; - let resultingVertex1 = resultingVertices[0].map((x) => - x.evaluate_to_constant(), - ); - let resultingVertex2 = resultingVertices[1].map((x) => - x.evaluate_to_constant(), - ); + let vertex1Changed = !vertex1.every( + (v, i) => Math.abs(v - resultingVertex1[i]) < tol, + ); + let vertex2Changed = !vertex2.every( + (v, i) => Math.abs(v - resultingVertex2[i]) < tol, + ); - let tol = 1e-6; + if (vertex1Changed) { + if (!vertex2Changed) { + // only vertex 1 changed + // keep directionWithRadius the same + // adjust center to put vertex 1 at its new location - let vertex1Changed = !vertex1.every( - (v, i) => Math.abs(v - resultingVertex1[i]) < tol, - ); - let vertex2Changed = !vertex2.every( - (v, i) => Math.abs(v - resultingVertex2[i]) < tol, - ); + let newCenter = [ + resultingVertex1[0] - directionWithRadius[0], + resultingVertex1[1] - directionWithRadius[1], + ]; - if (vertex1Changed) { - if (!vertex2Changed) { - // only vertex 1 changed + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "centerComponents", + value: newCenter, + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "directionWithRadius", + value: directionWithRadius, + }, + ]; + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } + } else if (vertex2Changed) { + // only vertex 2 changed // keep directionWithRadius the same - // adjust center to put vertex 1 at its new location + // adjust center to put vertex 2 at its new location let newCenter = [ - resultingVertex1[0] - directionWithRadius[0], - resultingVertex1[1] - directionWithRadius[1], + resultingVertex2[0] - directionWithRadius2[0], + resultingVertex2[1] - directionWithRadius2[1], ]; let newInstructions = [ @@ -2055,37 +2088,6 @@ export default class RegularPolygon extends Polygon { skipRendererUpdate, }); } - } else if (vertex2Changed) { - // only vertex 2 changed - // keep directionWithRadius the same - // adjust center to put vertex 2 at its new location - - let newCenter = [ - resultingVertex2[0] - directionWithRadius2[0], - resultingVertex2[1] - directionWithRadius2[1], - ]; - - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "centerComponents", - value: newCenter, - }, - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "directionWithRadius", - value: directionWithRadius, - }, - ]; - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); } } diff --git a/src/Core/components/Vector.js b/src/Core/components/Vector.js index 41cd85d291..7ba58f1a4c 100644 --- a/src/Core/components/Vector.js +++ b/src/Core/components/Vector.js @@ -98,6 +98,13 @@ export default class Vector extends GraphicalComponent { public: true, }; + attributes.allowFlexibleMotion = { + createComponentOfType: "boolean", + createStateVariable: "allowFlexibleMotion", + defaultValue: false, + public: true, + }; + return attributes; } @@ -2326,6 +2333,8 @@ export default class Vector extends GraphicalComponent { } } + // Note: we set skipRendererUpdate to true + // so that we can make further adjustments before the renderers are updated if (transient) { await this.coreFunctions.performUpdate({ updateInstructions, @@ -2355,72 +2364,78 @@ export default class Vector extends GraphicalComponent { }); } - // if dragged the whole ray that is based on endpoint and through point, - // address case where only one point is constrained - // to make ray just translate in this case - - if ( - tailcoords !== undefined && - headcoords !== undefined && - (await this.stateValues.basedOnTail) && - (await this.stateValues.basedOnHead) - ) { - let numericalPoints = [tailcoords, headcoords]; - let resultingNumericalPoints = await this.stateValues.numericalEndpoints; - - let pointsChanged = []; - let nPointsChanged = 0; - - for (let [ind, pt] of numericalPoints.entries()) { - if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { - pointsChanged.push(ind); - nPointsChanged++; + // unless allowFlexibleMotion is set + // we attempt to keep the vector displacement fixed + // even if one of the points defining it is constrained + if (!(await this.stateValues.allowFlexibleMotion)) { + // if dragged the whole vector that is based on tail and head, + // address case where only one point is constrained + // to make vector just translate in this case + + if ( + tailcoords !== undefined && + headcoords !== undefined && + (await this.stateValues.basedOnTail) && + (await this.stateValues.basedOnHead) + ) { + let numericalPoints = [tailcoords, headcoords]; + let resultingNumericalPoints = await this.stateValues + .numericalEndpoints; + + let pointsChanged = []; + let nPointsChanged = 0; + + for (let [ind, pt] of numericalPoints.entries()) { + if (!pt.every((v, i) => v === resultingNumericalPoints[ind][i])) { + pointsChanged.push(ind); + nPointsChanged++; + } } - } - if (nPointsChanged === 1) { - // one point was altered from the requested location. + if (nPointsChanged === 1) { + // one point was altered from the requested location. - let changedInd = pointsChanged[0]; + let changedInd = pointsChanged[0]; - let orig1 = numericalPoints[changedInd]; - let changed1 = resultingNumericalPoints[changedInd]; - let changevec1 = orig1.map((v, i) => v - changed1[i]); + let orig1 = numericalPoints[changedInd]; + let changed1 = resultingNumericalPoints[changedInd]; + let changevec1 = orig1.map((v, i) => v - changed1[i]); - let newNumericalPoints = []; + let newNumericalPoints = []; - for (let i = 0; i < 2; i++) { - if (i === changedInd) { - newNumericalPoints.push(resultingNumericalPoints[i]); - } else { - newNumericalPoints.push( - numericalPoints[i].map((v, j) => v - changevec1[j]), - ); + for (let i = 0; i < 2; i++) { + if (i === changedInd) { + newNumericalPoints.push(resultingNumericalPoints[i]); + } else { + newNumericalPoints.push( + numericalPoints[i].map((v, j) => v - changevec1[j]), + ); + } } - } - let newInstructions = [ - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "tail", - value: newNumericalPoints[0].map((x) => me.fromAst(x)), - }, - { - updateType: "updateValue", - componentName: this.componentName, - stateVariable: "head", - value: newNumericalPoints[1].map((x) => me.fromAst(x)), - }, - ]; - - return await this.coreFunctions.performUpdate({ - updateInstructions: newInstructions, - transient, - actionId, - sourceInformation, - skipRendererUpdate, - }); + let newInstructions = [ + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "tail", + value: newNumericalPoints[0].map((x) => me.fromAst(x)), + }, + { + updateType: "updateValue", + componentName: this.componentName, + stateVariable: "head", + value: newNumericalPoints[1].map((x) => me.fromAst(x)), + }, + ]; + + return await this.coreFunctions.performUpdate({ + updateInstructions: newInstructions, + transient, + actionId, + sourceInformation, + skipRendererUpdate, + }); + } } } From c635d9a1d8f399a5c00443e098a190b4f8fedae7 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Tue, 25 Apr 2023 04:30:17 +0000 Subject: [PATCH 4/7] add tests --- cypress/e2e/DoenetML/tagSpecific/circle.cy.js | 8530 ++++++++++++----- cypress/e2e/DoenetML/tagSpecific/line.cy.js | 200 +- .../DoenetML/tagSpecific/linesegment.cy.js | 192 + .../e2e/DoenetML/tagSpecific/polygon.cy.js | 704 ++ .../e2e/DoenetML/tagSpecific/polyline.cy.js | 704 ++ cypress/e2e/DoenetML/tagSpecific/ray.cy.js | 682 ++ .../e2e/DoenetML/tagSpecific/rectangle.cy.js | 379 +- .../tagSpecific/regularPolygon2.cy.js | 366 +- cypress/e2e/DoenetML/tagSpecific/vector.cy.js | 682 ++ 9 files changed, 10176 insertions(+), 2263 deletions(-) diff --git a/cypress/e2e/DoenetML/tagSpecific/circle.cy.js b/cypress/e2e/DoenetML/tagSpecific/circle.cy.js index c868735bab..f33098e91b 100644 --- a/cypress/e2e/DoenetML/tagSpecific/circle.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/circle.cy.js @@ -14417,6 +14417,7 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let desiredHeight = 5; let actualHeight = 11 / 4; + // Note on the result of allowFlexibleMotion being false (the default). // The following isn't the desired behavior, but it is a result of the situation // appearing to be that of a constrained center and a free through point when moving the circle. // (The through point ends up where requested but the center got altered.) @@ -14426,7 +14427,7 @@ describe("Circle Tag Tests", function () { // between the through point and the center. // The attempt to move the through point a second time to preserve the radius yield this result: // Given previous radius is 2, would move through point to (-3, 5+2), - // so that center of circle would be initially be (-3,(5+2)/2). + // so that center of circle would initially be (-3,(5+2)/2). // Since center changed from given value but through point didn't, // it will attempt to move through point back to radius 2 above center, // i.e., to (-3, (5+2)/2+2)) = (-3, 11/2) @@ -14502,8 +14503,9 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let desiredHeight = -31; let actualHeight = -5 / 2; + // Note on the result of allowFlexibleMotion being false (the default). // Given previous radius is 7, would move through point to (4, -24), - // so that center of circle would be initially be (4,-12). + // so that center of circle would initially be (4,-12). // Since center changed from given value but through point didn't, // it will attempt to move through point back to radius 7 above center, // i.e., to (4, -5) @@ -14543,7 +14545,6 @@ describe("Circle Tag Tests", function () { cy.log("move circle back up with center point"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); let desiredHeight = 4; let actualHeight = 4; // since moving point itself await win.callAction1({ @@ -14578,14 +14579,14 @@ describe("Circle Tag Tests", function () { }); }); - it("circle where through point depends on center", () => { + it("circle where center depends on through point, allow flexible motion", () => { cy.window().then(async (win) => { win.postMessage( { doenetML: ` a - + @@ -14618,21 +14619,10 @@ describe("Circle Tag Tests", function () { cy.log("move circle"); cy.window().then(async (win) => { let desiredHeight = 5; - let actualHeight = 11 / 4; - // The following isn't the desired behavior, but it is a result of the situation - // appearing to be that of a constrained center and a free through point when moving the circle. - // (The through point ends up where requested but the center got altered.) - // Since we care about that situation (see test "circle with center and through point, center constrained") - // but don't care as much about this contrived situation, - // we live with this more complicated behavior in the case where we have this strange relationship - // between the through point and the center. - // The attempt to move the through point a second time to preserve the radius yield this result: + let actualHeight = (5 + 2) / 2; // Given previous radius is 2, would move through point to (-3, 5+2), - // so that center of circle would be initially be (-3,(5+2)/2). - // Since center changed from given value but through point didn't, - // it will attempt to move through point back to radius 2 above center, - // i.e., to (-3, (5+2)/2+2)) = (-3, 11/2) - // which will make the center be (-3, 11/4) + // so that center of circle will be (-3,(5+2)/2). + // With allowFlexibleMotion set to true, no additional adjustments are made await win.callAction1({ actionName: "moveCircle", componentName: "/_circle1", @@ -14703,13 +14693,10 @@ describe("Circle Tag Tests", function () { cy.log("move circle below x-axis"); cy.window().then(async (win) => { let desiredHeight = -31; - let actualHeight = -5 / 2; + let actualHeight = -12; // Given previous radius is 7, would move through point to (4, -24), - // so that center of circle would be initially be (4,-12). - // Since center changed from given value but through point didn't, - // it will attempt to move through point back to radius 7 above center, - // i.e., to (4, -5) - // which will make the center be (4, -5/2) + // so that center of circle will be (4,-12). + // With allowFlexibleMotion set to true, no additional adjustments are made await win.callAction1({ actionName: "moveCircle", componentName: "/_circle1", @@ -14779,14 +14766,14 @@ describe("Circle Tag Tests", function () { }); }); - it("circle where one center component depends on other center component", () => { + it("circle where through point depends on center", () => { cy.window().then(async (win) => { win.postMessage( { doenetML: ` a - + @@ -14804,7 +14791,7 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); expect(stateVariables["/_circle1"].stateValues.center).eqls([1, 2]); - expect(stateVariables["/_circle1"].stateValues.radius).eq(1); + expect(stateVariables["/_circle1"].stateValues.radius).eq(2); expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ "vector", 1, @@ -14812,14 +14799,29 @@ describe("Circle Tag Tests", function () { ]); cy.get(cesc("#\\/radiusNumber") + " .mjx-mrow").should( "contain.text", - nInDOM(1), + nInDOM(2), ); }); cy.log("move circle"); cy.window().then(async (win) => { let desiredHeight = 5; - let actualHeight = -2; + let actualHeight = 11 / 4; + // Note on the result of allowFlexibleMotion being false (the default). + // The following isn't the desired behavior, but it is a result of the situation + // appearing to be that of a constrained center and a free through point when moving the circle. + // (The through point ends up where requested but the center got altered.) + // Since we care about that situation (see test "circle with center and through point, center constrained") + // but don't care as much about this contrived situation, + // we live with this more complicated behavior in the case where we have this strange relationship + // between the through point and the center. + // The attempt to move the through point a second time to preserve the radius yield this result: + // Given previous radius is 2, would move through point to (-3, 5+2), + // so that center of circle would initially be (-3,(5+2)/2). + // Since center changed from given value but through point didn't, + // it will attempt to move through point back to radius 2 above center, + // i.e., to (-3, (5+2)/2+2)) = (-3, 11/2) + // which will make the center be (-3, 11/4) await win.callAction1({ actionName: "moveCircle", componentName: "/_circle1", @@ -14831,7 +14833,10 @@ describe("Circle Tag Tests", function () { "contain.text", `${nInDOM(actualHeight)}`, ); - cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(1)); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(actualHeight), + ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); @@ -14840,7 +14845,7 @@ describe("Circle Tag Tests", function () { -3, actualHeight, ]); - expect(stateVariables["/_circle1"].stateValues.radius).eq(1); + expect(stateVariables["/_circle1"].stateValues.radius).eq(actualHeight); expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ "vector", -3, @@ -14852,7 +14857,7 @@ describe("Circle Tag Tests", function () { cy.log("move center point"); cy.window().then(async (win) => { let desiredHeight = 7; - let actualHeight = 9; // since moving center itself + let actualHeight = 7; // since moving center itself await win.callAction1({ actionName: "movePoint", componentName: "/centerPoint", @@ -14864,7 +14869,10 @@ describe("Circle Tag Tests", function () { "contain.text", `${nInDOM(actualHeight)}`, ); - cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(1)); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(actualHeight), + ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); @@ -14872,7 +14880,7 @@ describe("Circle Tag Tests", function () { 8, actualHeight, ]); - expect(stateVariables["/_circle1"].stateValues.radius).eq(1); + expect(stateVariables["/_circle1"].stateValues.radius).eq(actualHeight); expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ "vector", 8, @@ -14880,23 +14888,96 @@ describe("Circle Tag Tests", function () { ]); }); }); + + cy.log("move circle below x-axis"); + cy.window().then(async (win) => { + let desiredHeight = -31; + let actualHeight = -5 / 2; + // Note on the result of allowFlexibleMotion being false (the default). + // Given previous radius is 7, would move through point to (4, -24), + // so that center of circle would initially be (4,-12). + // Since center changed from given value but through point didn't, + // it will attempt to move through point back to radius 7 above center, + // i.e., to (4, -5) + // which will make the center be (4, -5/2) + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [4, desiredHeight] }, + }); + + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(${nInDOM(4)}`); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(actualHeight)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(-actualHeight), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center).eqls([ + 4, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq( + -actualHeight, + ); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 4, + actualHeight, + ]); + }); + }); + + cy.log("move circle back up with center point"); + cy.window().then(async (win) => { + let desiredHeight = 4; + let actualHeight = 4; // since moving point itself + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: 1, y: desiredHeight }, + }); + + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(${nInDOM(1)}`); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(actualHeight)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(actualHeight), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center).eqls([ + 1, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq(actualHeight); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 1, + actualHeight, + ]); + }); + }); }); - it("circle where radius depends on two through points", () => { + it("circle where through point depends on center, allow flexible motion", () => { cy.window().then(async (win) => { win.postMessage( { doenetML: ` a - - abs( - -) - - (1,2) - (3,4) - - + + @@ -14910,398 +14991,292 @@ describe("Circle Tag Tests", function () { cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load - let t1x = 1, - t1y = 2; - let t2x = 3, - t2y = 4; - cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - let r = Math.abs(t1x - t2x); - expect(stateVariables["/_circle1"].stateValues.radius).eq(r); - expect(stateVariables["/_circle1"].stateValues.throughPoints[0]).eqls([ - t1x, - t1y, - ]); - expect(stateVariables["/_circle1"].stateValues.throughPoints[1]).eqls([ - t2x, - t2y, - ]); - expect(await stateVariables["/TP1"].stateValues.coords).eqls([ - "vector", - t1x, - t1y, - ]); - expect(await stateVariables["/TP2"].stateValues.coords).eqls([ + expect(stateVariables["/_circle1"].stateValues.center).eqls([1, 2]); + expect(stateVariables["/_circle1"].stateValues.radius).eq(2); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ "vector", - t2x, - t2y, + 1, + 2, ]); cy.get(cesc("#\\/radiusNumber") + " .mjx-mrow").should( "contain.text", - nInDOM(r), + nInDOM(2), ); }); cy.log("move circle"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - let numericalCenter = - stateVariables["/_circle1"].stateValues.numericalCenter; - let dx = 2, - dy = -3; - let newCenter = [numericalCenter[0] + dx, numericalCenter[1] + dy]; - t1x += dx; - t1y += dy; - t2x += dx; - t2y += dy; - + let desiredHeight = 5; + let actualHeight = (5 + 2) / 2; + // Given previous radius is 2, would move through point to (-3, 5+2), + // so that center of circle will be (-3,(5+2)/2). + // With allowFlexibleMotion set to true, no additional adjustments are made await win.callAction1({ actionName: "moveCircle", componentName: "/_circle1", - args: { center: newCenter }, + args: { center: [-3, desiredHeight] }, }); - let r = Math.abs(t1x - t2x); - + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(${nInDOM(-3)}`); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(newCenter[0])}`, + `${nInDOM(actualHeight)}`, ); - cy.get(cesc("#\\/centerPoint2")).should( + cy.get(cesc("#\\/radiusNumber")).should( "contain.text", - `${nInDOM(newCenter[1])}`, + nInDOM(actualHeight), ); - cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); + expect(stateVariables["/_circle1"].stateValues.center).eqls([ + -3, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq(actualHeight); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + -3, + actualHeight, + ]); + }); + }); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], - ).closeTo(t1x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], - ).closeTo(t1y, 1e-12); + cy.log("move center point"); + cy.window().then(async (win) => { + let desiredHeight = 7; + let actualHeight = 7; // since moving center itself + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: 8, y: desiredHeight }, + }); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], - ).closeTo(t2x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], - ).closeTo(t2y, 1e-12); + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(${nInDOM(8)}`); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(actualHeight)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(actualHeight), + ); - expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( - t1x, - 1e-12, - ); - expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( - t1y, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( - t2x, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( - t2y, - 1e-12, - ); - - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(newCenter[0], 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(newCenter[1], 1e-12); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center).eqls([ + 8, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq(actualHeight); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 8, + actualHeight, + ]); }); }); - cy.log("move center point"); + cy.log("move circle below x-axis"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); + let desiredHeight = -31; + let actualHeight = -12; + // Given previous radius is 7, would move through point to (4, -24), + // so that center of circle will be (4,-12). + // With allowFlexibleMotion set to true, no additional adjustments are made + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [4, desiredHeight] }, + }); - let numericalCenter = - stateVariables["/_circle1"].stateValues.numericalCenter; - let dx = -5, - dy = -2; - let newCenter = [numericalCenter[0] + dx, numericalCenter[1] + dy]; - t1x += dx; - t1y += dy; - t2x += dx; - t2y += dy; + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(${nInDOM(4)}`); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(actualHeight)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(-actualHeight), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center).eqls([ + 4, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq( + -actualHeight, + ); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 4, + actualHeight, + ]); + }); + }); + cy.log("move circle back up with center point"); + cy.window().then(async (win) => { + let desiredHeight = 4; + let actualHeight = 4; // since moving point itself await win.callAction1({ actionName: "movePoint", componentName: "/centerPoint", - args: { x: newCenter[0], y: newCenter[1] }, + args: { x: 1, y: desiredHeight }, }); - let r = Math.abs(t1x - t2x); - + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(${nInDOM(1)}`); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(newCenter[0])}`, + `${nInDOM(actualHeight)}`, ); - cy.get(cesc("#\\/centerPoint2")).should( + cy.get(cesc("#\\/radiusNumber")).should( "contain.text", - `${nInDOM(newCenter[1])}`, + nInDOM(actualHeight), ); - cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); + expect(stateVariables["/_circle1"].stateValues.center).eqls([ + 1, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq(actualHeight); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 1, + actualHeight, + ]); + }); + }); + }); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], - ).closeTo(t1x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], - ).closeTo(t1y, 1e-12); + it("circle where one center component depends on other center component", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + + + - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], - ).closeTo(t2x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], - ).closeTo(t2y, 1e-12); + + - expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( - t1x, - 1e-12, - ); - expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( - t1y, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( - t2x, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( - t2y, - 1e-12, - ); + `, + }, + "*", + ); + }); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(newCenter[0], 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(newCenter[1], 1e-12); - }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center).eqls([1, 2]); + expect(stateVariables["/_circle1"].stateValues.radius).eq(1); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 1, + 2, + ]); + cy.get(cesc("#\\/radiusNumber") + " .mjx-mrow").should( + "contain.text", + nInDOM(1), + ); }); - cy.log("move first through point"); + cy.log("move circle"); cy.window().then(async (win) => { - t1x = 6; - t1y = 3; + let desiredHeight = 5; + let actualHeight = -2; await win.callAction1({ - actionName: "movePoint", - componentName: "/TP1", - args: { x: t1x, y: t1y }, + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [-3, desiredHeight] }, }); - let r = Math.abs(t1x - t2x); - - cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(${nInDOM(-3)}`); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(actualHeight)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(1)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); - - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], - ).closeTo(t1x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], - ).closeTo(t1y, 1e-12); - - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], - ).closeTo(t2x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], - ).closeTo(t2y, 1e-12); - expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( - t1x, - 1e-12, - ); - expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( - t1y, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( - t2x, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( - t2y, - 1e-12, - ); + expect(stateVariables["/_circle1"].stateValues.center).eqls([ + -3, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq(1); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + -3, + actualHeight, + ]); }); }); - cy.log("move second through point under first through point"); + cy.log("move center point"); cy.window().then(async (win) => { - t2x = 5; - t2y = -3; + let desiredHeight = 7; + let actualHeight = 9; // since moving center itself await win.callAction1({ actionName: "movePoint", - componentName: "/TP2", - args: { x: t2x, y: t2y }, + componentName: "/centerPoint", + args: { x: 8, y: desiredHeight }, }); - let r = Math.abs(t1x - t2x); - - cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); - - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], - ).closeTo(t1x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], - ).closeTo(t1y, 1e-12); - - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], - ).closeTo(t2x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], - ).closeTo(t2y, 1e-12); - - expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( - t1x, - 1e-12, - ); - expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( - t1y, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( - t2x, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( - t2y, - 1e-12, - ); - - expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).false; - expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).false; - }); - }); - - cy.log("move second through point close enough to make circle"); - cy.window().then(async (win) => { - t2y = 1.5; - await win.callAction1({ - actionName: "movePoint", - componentName: "/TP2", - args: { x: t2x, y: t2y }, - }); - - let r = Math.abs(t1x - t2x); - - cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(${nInDOM(8)}`); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(actualHeight)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(1)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); - - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], - ).closeTo(t1x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], - ).closeTo(t1y, 1e-12); - - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], - ).closeTo(t2x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], - ).closeTo(t2y, 1e-12); - - expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( - t1x, - 1e-12, - ); - expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( - t1y, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( - t2x, - 1e-12, - ); - expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( - t2y, - 1e-12, - ); - - expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).true; - expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).true; + expect(stateVariables["/_circle1"].stateValues.center).eqls([ + 8, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq(1); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 8, + actualHeight, + ]); }); }); }); - it("circle with dependencies among radius and two through points", () => { + it("circle where radius depends on two through points", () => { cy.window().then(async (win) => { win.postMessage( { doenetML: ` a - - + + abs( + -) (1,2) - - + (3,4) + + `, @@ -15314,13 +15289,12 @@ describe("Circle Tag Tests", function () { let t1x = 1, t1y = 2; - let t2x = 2, - t2y = 3; + let t2x = 3, + t2y = 4; cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - - let r = t1x; + let r = Math.abs(t1x - t2x); expect(stateVariables["/_circle1"].stateValues.radius).eq(r); expect(stateVariables["/_circle1"].stateValues.throughPoints[0]).eqls([ t1x, @@ -15340,7 +15314,6 @@ describe("Circle Tag Tests", function () { t2x, t2y, ]); - cy.get(cesc("#\\/radiusNumber") + " .mjx-mrow").should( "contain.text", nInDOM(r), @@ -15367,12 +15340,21 @@ describe("Circle Tag Tests", function () { args: { center: newCenter }, }); - let r = t1x; + let r = Math.abs(t1x - t2x); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(newCenter[0])}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(newCenter[1])}`, + ); cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( r, 1e-12, @@ -15410,15 +15392,11 @@ describe("Circle Tag Tests", function () { ); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(newCenter[0], 1e-12); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(newCenter[1], 1e-12); }); }); @@ -15428,7 +15406,7 @@ describe("Circle Tag Tests", function () { let numericalCenter = stateVariables["/_circle1"].stateValues.numericalCenter; - let dx = -1, + let dx = -5, dy = -2; let newCenter = [numericalCenter[0] + dx, numericalCenter[1] + dy]; t1x += dx; @@ -15442,8 +15420,16 @@ describe("Circle Tag Tests", function () { args: { x: newCenter[0], y: newCenter[1] }, }); - let r = t1x; + let r = Math.abs(t1x - t2x); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(newCenter[0])}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(newCenter[1])}`, + ); cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); cy.window().then(async (win) => { @@ -15485,15 +15471,11 @@ describe("Circle Tag Tests", function () { ); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(newCenter[0], 1e-12); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(newCenter[1], 1e-12); }); }); @@ -15507,8 +15489,7 @@ describe("Circle Tag Tests", function () { args: { x: t1x, y: t1y }, }); - let r = t1x; - t2x = t1x + 1; + let r = Math.abs(t1x - t2x); cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); @@ -15554,14 +15535,15 @@ describe("Circle Tag Tests", function () { cy.log("move second through point under first through point"); cy.window().then(async (win) => { - t2y = -9; + t2x = 5; + t2y = -3; await win.callAction1({ actionName: "movePoint", componentName: "/TP2", args: { x: t2x, y: t2y }, }); - let r = t1x; + let r = Math.abs(t1x - t2x); cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); @@ -15616,17 +15598,16 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("move second through point to the right"); + cy.log("move second through point close enough to make circle"); cy.window().then(async (win) => { - t2x = 8; + t2y = 1.5; await win.callAction1({ actionName: "movePoint", componentName: "/TP2", args: { x: t2x, y: t2y }, }); - t1x = t2x - 1; - let r = t1x; + let r = Math.abs(t1x - t2x); cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); @@ -15682,16 +15663,19 @@ describe("Circle Tag Tests", function () { }); }); - it("circle where through point 2 depends on through point 1", () => { + it("circle with dependencies among radius and two through points", () => { cy.window().then(async (win) => { win.postMessage( { doenetML: ` a + + + (1,2) - + @@ -15713,12 +15697,8 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - - let cnx = (t1x + t2x) / 2; - let cny = (t1y + t2y) / 2; - - expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + let r = t1x; + expect(stateVariables["/_circle1"].stateValues.radius).eq(r); expect(stateVariables["/_circle1"].stateValues.throughPoints[0]).eqls([ t1x, t1y, @@ -15738,28 +15718,21 @@ describe("Circle Tag Tests", function () { t2y, ]); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - cy.get(cesc("#\\/radiusNumber") + " .mjx-mrow").should( "contain.text", - nInDOM(Math.trunc(r * 100) / 100), + nInDOM(r), ); }); cy.log("move circle"); cy.window().then(async (win) => { - let cnx = (t1x + t2x) / 2; - let cny = (t1y + t2y) / 2; + let stateVariables = await win.returnAllStateVariables1(); + let numericalCenter = + stateVariables["/_circle1"].stateValues.numericalCenter; let dx = 2, dy = -3; - cnx += dx; - cny += dy; + let newCenter = [numericalCenter[0] + dx, numericalCenter[1] + dy]; t1x += dx; t1y += dy; t2x += dx; @@ -15768,15 +15741,12 @@ describe("Circle Tag Tests", function () { await win.callAction1({ actionName: "moveCircle", componentName: "/_circle1", - args: { center: [cnx, cny] }, + args: { center: newCenter }, }); - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let r = t1x; - cy.get(cesc("#\\/radiusNumber")).should( - "contain.text", - nInDOM(Math.trunc(r * 100) / 100), - ); + cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); @@ -15817,23 +15787,27 @@ describe("Circle Tag Tests", function () { ); expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).true; expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - }); - }); + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).true; + }); + }); cy.log("move center point"); cy.window().then(async (win) => { - let cnx = (t1x + t2x) / 2; - let cny = (t1y + t2y) / 2; + let stateVariables = await win.returnAllStateVariables1(); + let numericalCenter = + stateVariables["/_circle1"].stateValues.numericalCenter; let dx = -1, dy = -2; - cnx += dx; - cny += dy; + let newCenter = [numericalCenter[0] + dx, numericalCenter[1] + dy]; t1x += dx; t1y += dy; t2x += dx; @@ -15842,15 +15816,12 @@ describe("Circle Tag Tests", function () { await win.callAction1({ actionName: "movePoint", componentName: "/centerPoint", - args: { x: cnx, y: cny }, + args: { x: newCenter[0], y: newCenter[1] }, }); - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let r = t1x; - cy.get(cesc("#\\/radiusNumber")).should( - "contain.text", - nInDOM(Math.trunc(r * 100) / 100), - ); + cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); @@ -15891,11 +15862,15 @@ describe("Circle Tag Tests", function () { ); expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).true; expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).true; }); }); @@ -15909,20 +15884,66 @@ describe("Circle Tag Tests", function () { args: { x: t1x, y: t1y }, }); + let r = t1x; t2x = t1x + 1; - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - - let cnx = (t1x + t2x) / 2; - let cny = (t1y + t2y) / 2; - cy.get(cesc("#\\/radiusNumber")).should( - "contain.text", - nInDOM(Math.trunc(r * 100) / 100), - ); + cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], + ).closeTo(t1x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], + ).closeTo(t1y, 1e-12); + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], + ).closeTo(t2x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], + ).closeTo(t2y, 1e-12); + + expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( + t1x, + 1e-12, + ); + expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( + t1y, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( + t2x, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( + t2y, + 1e-12, + ); + }); + }); + + cy.log("move second through point under first through point"); + cy.window().then(async (win) => { + t2y = -9; + await win.callAction1({ + actionName: "movePoint", + componentName: "/TP2", + args: { x: t2x, y: t2y }, + }); + + let r = t1x; + + cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); expect(stateVariables["/_circle1"].stateValues.radius).closeTo( r, 1e-12, @@ -15960,18 +15981,21 @@ describe("Circle Tag Tests", function () { ); expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).false; expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).false; }); }); - cy.log("move second through point"); + cy.log("move second through point to the right"); cy.window().then(async (win) => { - t2x = -7; - t2y = -9; + t2x = 8; await win.callAction1({ actionName: "movePoint", componentName: "/TP2", @@ -15979,15 +16003,9 @@ describe("Circle Tag Tests", function () { }); t1x = t2x - 1; - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - - let cnx = (t1x + t2x) / 2; - let cny = (t1y + t2y) / 2; + let r = t1x; - cy.get(cesc("#\\/radiusNumber")).should( - "contain.text", - nInDOM(Math.trunc(r * 100) / 100), - ); + cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); @@ -16028,29 +16046,32 @@ describe("Circle Tag Tests", function () { ); expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).true; expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).true; }); }); }); - it("circle with dependencies among three through points", () => { + it("circle where through point 2 depends on through point 1", () => { cy.window().then(async (win) => { win.postMessage( { doenetML: ` a - (1,2) - - + (1,2) + + - `, @@ -16061,18 +16082,20 @@ describe("Circle Tag Tests", function () { cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load - let t1x = 2, - t1y = 3; - let t2x = 1, - t2y = 2; - let t3x = 3, - t3y = 5; + let t1x = 1, + t1y = 2; + let t2x = 2, + t2y = 3; cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) - .true; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let cnx = (t1x + t2x) / 2; + let cny = (t1y + t2y) / 2; + + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); expect(stateVariables["/_circle1"].stateValues.throughPoints[0]).eqls([ t1x, t1y, @@ -16081,10 +16104,6 @@ describe("Circle Tag Tests", function () { t2x, t2y, ]); - expect(stateVariables["/_circle1"].stateValues.throughPoints[2]).eqls([ - t3x, - t3y, - ]); expect(await stateVariables["/TP1"].stateValues.coords).eqls([ "vector", t1x, @@ -16095,24 +16114,13 @@ describe("Circle Tag Tests", function () { t2x, t2y, ]); - expect(await stateVariables["/TP3"].stateValues.coords).eqls([ - "vector", - t3x, - t3y, - ]); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).true; - - let r = stateVariables["/_circle1"].stateValues.radius; + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); cy.get(cesc("#\\/radiusNumber") + " .mjx-mrow").should( "contain.text", @@ -16122,12 +16130,8 @@ describe("Circle Tag Tests", function () { cy.log("move circle"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - let numericalCenter = - stateVariables["/_circle1"].stateValues.numericalCenter; - let cnx = numericalCenter[0]; - let cny = numericalCenter[1]; + let cnx = (t1x + t2x) / 2; + let cny = (t1y + t2y) / 2; let dx = 2, dy = -3; @@ -16137,10 +16141,6 @@ describe("Circle Tag Tests", function () { t1y += dy; t2x += dx; t2y += dy; - t3x += dx; - t3y += dy; - - let r = stateVariables["/_circle1"].stateValues.radius; await win.callAction1({ actionName: "moveCircle", @@ -16148,6 +16148,8 @@ describe("Circle Tag Tests", function () { args: { center: [cnx, cny] }, }); + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + cy.get(cesc("#\\/radiusNumber")).should( "contain.text", nInDOM(Math.trunc(r * 100) / 100), @@ -16174,13 +16176,6 @@ describe("Circle Tag Tests", function () { stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], ).closeTo(t2y, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], - ).closeTo(t3x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], - ).closeTo(t3y, 1e-12); - expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( t1x, 1e-12, @@ -16197,14 +16192,6 @@ describe("Circle Tag Tests", function () { t2y, 1e-12, ); - expect((await stateVariables["/TP3"].stateValues.xs)[0]).closeTo( - t3x, - 1e-12, - ); - expect((await stateVariables["/TP3"].stateValues.xs)[1]).closeTo( - t3y, - 1e-12, - ); expect( stateVariables["/_circle1"].stateValues.numericalCenter[0], @@ -16217,12 +16204,8 @@ describe("Circle Tag Tests", function () { cy.log("move center point"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - let numericalCenter = - stateVariables["/_circle1"].stateValues.numericalCenter; - let cnx = numericalCenter[0]; - let cny = numericalCenter[1]; + let cnx = (t1x + t2x) / 2; + let cny = (t1y + t2y) / 2; let dx = -1, dy = -2; @@ -16232,10 +16215,6 @@ describe("Circle Tag Tests", function () { t1y += dy; t2x += dx; t2y += dy; - t3x += dx; - t3y += dy; - - let r = stateVariables["/_circle1"].stateValues.radius; await win.callAction1({ actionName: "movePoint", @@ -16243,6 +16222,8 @@ describe("Circle Tag Tests", function () { args: { x: cnx, y: cny }, }); + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + cy.get(cesc("#\\/radiusNumber")).should( "contain.text", nInDOM(Math.trunc(r * 100) / 100), @@ -16269,13 +16250,6 @@ describe("Circle Tag Tests", function () { stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], ).closeTo(t2y, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], - ).closeTo(t3x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], - ).closeTo(t3y, 1e-12); - expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( t1x, 1e-12, @@ -16292,14 +16266,6 @@ describe("Circle Tag Tests", function () { t2y, 1e-12, ); - expect((await stateVariables["/TP3"].stateValues.xs)[0]).closeTo( - t3x, - 1e-12, - ); - expect((await stateVariables["/TP3"].stateValues.xs)[1]).closeTo( - t3y, - 1e-12, - ); expect( stateVariables["/_circle1"].stateValues.numericalCenter[0], @@ -16320,18 +16286,24 @@ describe("Circle Tag Tests", function () { args: { x: t1x, y: t1y }, }); - t3x = t1x + 1; - t2x = t1x - 1; + t2x = t1x + 1; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - cy.get(cesc("#\\/TP1")).should( + let cnx = (t1x + t2x) / 2; + let cny = (t1y + t2y) / 2; + + cy.get(cesc("#\\/radiusNumber")).should( "contain.text", - `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, + nInDOM(Math.trunc(r * 100) / 100), ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) - .true; + + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); expect( stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], @@ -16347,13 +16319,6 @@ describe("Circle Tag Tests", function () { stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], ).closeTo(t2y, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], - ).closeTo(t3x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], - ).closeTo(t3y, 1e-12); - expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( t1x, 1e-12, @@ -16370,25 +16335,13 @@ describe("Circle Tag Tests", function () { t2y, 1e-12, ); - expect((await stateVariables["/TP3"].stateValues.xs)[0]).closeTo( - t3x, - 1e-12, - ); - expect((await stateVariables["/TP3"].stateValues.xs)[1]).closeTo( - t3y, - 1e-12, - ); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); }); }); @@ -16402,18 +16355,23 @@ describe("Circle Tag Tests", function () { args: { x: t2x, y: t2y }, }); - t1x = t2x + 1; - t3x = t1x + 1; + t1x = t2x - 1; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - cy.get(cesc("#\\/TP1")).should( + let cnx = (t1x + t2x) / 2; + let cny = (t1y + t2y) / 2; + + cy.get(cesc("#\\/radiusNumber")).should( "contain.text", - `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, + nInDOM(Math.trunc(r * 100) / 100), ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) - .true; + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); expect( stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], @@ -16429,13 +16387,6 @@ describe("Circle Tag Tests", function () { stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], ).closeTo(t2y, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], - ).closeTo(t3x, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], - ).closeTo(t3y, 1e-12); - expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( t1x, 1e-12, @@ -16452,50 +16403,139 @@ describe("Circle Tag Tests", function () { t2y, 1e-12, ); - expect((await stateVariables["/TP3"].stateValues.xs)[0]).closeTo( - t3x, - 1e-12, - ); - expect((await stateVariables["/TP3"].stateValues.xs)[1]).closeTo( - t3y, - 1e-12, - ); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); }); }); + }); - cy.log("move third through point"); + it("circle with dependencies among three through points", () => { cy.window().then(async (win) => { - t3x = 1; - t3y = -2; + win.postMessage( + { + doenetML: ` + a + + (1,2) + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + let t1x = 2, + t1y = 3; + let t2x = 1, + t2y = 2; + let t3x = 3, + t3y = 5; + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) + .true; + expect(stateVariables["/_circle1"].stateValues.throughPoints[0]).eqls([ + t1x, + t1y, + ]); + expect(stateVariables["/_circle1"].stateValues.throughPoints[1]).eqls([ + t2x, + t2y, + ]); + expect(stateVariables["/_circle1"].stateValues.throughPoints[2]).eqls([ + t3x, + t3y, + ]); + expect(await stateVariables["/TP1"].stateValues.coords).eqls([ + "vector", + t1x, + t1y, + ]); + expect(await stateVariables["/TP2"].stateValues.coords).eqls([ + "vector", + t2x, + t2y, + ]); + expect(await stateVariables["/TP3"].stateValues.coords).eqls([ + "vector", + t3x, + t3y, + ]); + + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).true; + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).true; + + let r = stateVariables["/_circle1"].stateValues.radius; + + cy.get(cesc("#\\/radiusNumber") + " .mjx-mrow").should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + cy.log("move circle"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let numericalCenter = + stateVariables["/_circle1"].stateValues.numericalCenter; + let cnx = numericalCenter[0]; + let cny = numericalCenter[1]; + + let dx = 2, + dy = -3; + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; + + let r = stateVariables["/_circle1"].stateValues.radius; + await win.callAction1({ - actionName: "movePoint", - componentName: "/TP3", - args: { x: t3x, y: t3y }, + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [cnx, cny] }, }); - t1x = t3x - 1; - t2x = t1x - 1; - - cy.get(cesc("#\\/TP1")).should( + cy.get(cesc("#\\/radiusNumber")).should( "contain.text", - `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, + nInDOM(Math.trunc(r * 100) / 100), ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) - .true; + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); expect( stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], @@ -16544,224 +16584,412 @@ describe("Circle Tag Tests", function () { ); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).true; + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); }); }); - }); - it("essential center can combine coordinates", () => { + cy.log("move center point"); cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - - - (, - ) - - - + let stateVariables = await win.returnAllStateVariables1(); - + let numericalCenter = + stateVariables["/_circle1"].stateValues.numericalCenter; + let cnx = numericalCenter[0]; + let cny = numericalCenter[1]; - `, - }, - "*", - ); - }); + let dx = -1, + dy = -2; + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + let r = stateVariables["/_circle1"].stateValues.radius; - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - expect(stateVariables["/_circle1"].stateValues.numericalRadius).eq(1); - expect(stateVariables["/_circle1"].stateValues.numericalCenter).eqls([ - 0, 0, - ]); - expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ - "vector", - 0, - 0, - ]); - expect(stateVariables["/_point1"].stateValues.coords).eqls([ - "vector", - 0, - 0, - ]); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(0)},${nInDOM(0)})`, - ); - }); - - cy.log("move circle"); - cy.window().then(async (win) => { await win.callAction1({ - actionName: "moveCircle", - componentName: "/_circle1", - args: { center: [-7, 2] }, + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: cnx, y: cny }, }); - cy.get(cesc("#\\/centerPoint2")).should( + cy.get(cesc("#\\/radiusNumber")).should( "contain.text", - `(${nInDOM(-7)},${nInDOM(2)})`, + nInDOM(Math.trunc(r * 100) / 100), ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.numericalCenter).eqls([ - -7, 2, - ]); - expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ - "vector", - -7, - 2, - ]); - expect(stateVariables["/_point1"].stateValues.coords).eqls([ - "vector", - 2, - -7, - ]); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], + ).closeTo(t1x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], + ).closeTo(t1y, 1e-12); + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], + ).closeTo(t2x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], + ).closeTo(t2y, 1e-12); + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], + ).closeTo(t3x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], + ).closeTo(t3y, 1e-12); + + expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( + t1x, + 1e-12, + ); + expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( + t1y, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( + t2x, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( + t2y, + 1e-12, + ); + expect((await stateVariables["/TP3"].stateValues.xs)[0]).closeTo( + t3x, + 1e-12, + ); + expect((await stateVariables["/TP3"].stateValues.xs)[1]).closeTo( + t3y, + 1e-12, + ); + + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); }); }); - cy.log("move flipped point"); + cy.log("move first through point"); cy.window().then(async (win) => { + t1x = 6; + t1y = 3; await win.callAction1({ actionName: "movePoint", - componentName: "/_point1", - args: { x: -3, y: -5 }, + componentName: "/TP1", + args: { x: t1x, y: t1y }, }); - cy.get(cesc("#\\/centerPoint2")).should( + t3x = t1x + 1; + t2x = t1x - 1; + + cy.get(cesc("#\\/TP1")).should( "contain.text", - `(${nInDOM(-5)},${nInDOM(-3)})`, + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.numericalCenter).eqls([ - -5, -3, - ]); - expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ - "vector", - -5, - -3, - ]); - expect(stateVariables["/_point1"].stateValues.coords).eqls([ - "vector", - -3, - -5, - ]); + expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) + .true; + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], + ).closeTo(t1x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], + ).closeTo(t1y, 1e-12); + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], + ).closeTo(t2x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], + ).closeTo(t2y, 1e-12); + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], + ).closeTo(t3x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], + ).closeTo(t3y, 1e-12); + + expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( + t1x, + 1e-12, + ); + expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( + t1y, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( + t2x, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( + t2y, + 1e-12, + ); + expect((await stateVariables["/TP3"].stateValues.xs)[0]).closeTo( + t3x, + 1e-12, + ); + expect((await stateVariables["/TP3"].stateValues.xs)[1]).closeTo( + t3y, + 1e-12, + ); + + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).true; + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).true; }); }); - cy.log("move center point"); + cy.log("move second through point"); cy.window().then(async (win) => { + t2x = -7; + t2y = -9; await win.callAction1({ actionName: "movePoint", - componentName: "/centerPoint", - args: { x: 1, y: -4 }, + componentName: "/TP2", + args: { x: t2x, y: t2y }, }); - cy.get(cesc("#\\/centerPoint2")).should( + t1x = t2x + 1; + t3x = t1x + 1; + + cy.get(cesc("#\\/TP1")).should( "contain.text", - `(${nInDOM(1)},${nInDOM(-4)})`, + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.numericalCenter).eqls([ - 1, -4, - ]); - expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ - "vector", - 1, - -4, - ]); - expect(stateVariables["/_point1"].stateValues.coords).eqls([ - "vector", - -4, - 1, - ]); - }); - }); - }); - - it("handle initially undefined center", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a -

Center:

- - - - - - + expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) + .true; - + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], + ).closeTo(t1x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], + ).closeTo(t1y, 1e-12); - `, - }, - "*", - ); - }); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], + ).closeTo(t2x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], + ).closeTo(t2y, 1e-12); - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], + ).closeTo(t3x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], + ).closeTo(t3y, 1e-12); + + expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( + t1x, + 1e-12, + ); + expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( + t1y, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( + t2x, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( + t2y, + 1e-12, + ); + expect((await stateVariables["/TP3"].stateValues.xs)[0]).closeTo( + t3x, + 1e-12, + ); + expect((await stateVariables["/TP3"].stateValues.xs)[1]).closeTo( + t3y, + 1e-12, + ); + + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).true; + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).true; + }); + }); + cy.log("move third through point"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([ - NaN, - NaN, - ]); - expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); - expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([ - NaN, - NaN, - ]); - expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); - cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(_,_)`); + t3x = 1; + t3y = -2; + await win.callAction1({ + actionName: "movePoint", + componentName: "/TP3", + args: { x: t3x, y: t3y }, + }); + + t1x = t3x - 1; + t2x = t1x - 1; + + cy.get(cesc("#\\/TP1")).should( + "contain.text", + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) + .true; + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], + ).closeTo(t1x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], + ).closeTo(t1y, 1e-12); + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], + ).closeTo(t2x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], + ).closeTo(t2y, 1e-12); + + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], + ).closeTo(t3x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], + ).closeTo(t3y, 1e-12); + + expect((await stateVariables["/TP1"].stateValues.xs)[0]).closeTo( + t1x, + 1e-12, + ); + expect((await stateVariables["/TP1"].stateValues.xs)[1]).closeTo( + t1y, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[0]).closeTo( + t2x, + 1e-12, + ); + expect((await stateVariables["/TP2"].stateValues.xs)[1]).closeTo( + t2y, + 1e-12, + ); + expect((await stateVariables["/TP3"].stateValues.xs)[0]).closeTo( + t3x, + 1e-12, + ); + expect((await stateVariables["/TP3"].stateValues.xs)[1]).closeTo( + t3y, + 1e-12, + ); + + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).true; + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).true; + }); }); + }); - cy.log("enter point for center"); - cy.get(cesc("#\\/c") + " textarea").type("(2,1){enter}", { force: true }); + it("essential center can combine coordinates", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + + + (, + ) + + + - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(2)},${nInDOM(1)})`, - ); + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([2, 1]); - expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); - expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([2, 1]); - expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); + + expect(stateVariables["/_circle1"].stateValues.numericalRadius).eq(1); + expect(stateVariables["/_circle1"].stateValues.numericalCenter).eqls([ + 0, 0, + ]); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 0, + 0, + ]); + expect(stateVariables["/_point1"].stateValues.coords).eqls([ + "vector", + 0, + 0, + ]); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(0)},${nInDOM(0)})`, + ); }); - cy.log(`move circle`); + cy.log("move circle"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); await win.callAction1({ actionName: "moveCircle", - componentName: "/circ", + componentName: "/_circle1", args: { center: [-7, 2] }, }); + cy.get(cesc("#\\/centerPoint2")).should( "contain.text", `(${nInDOM(-7)},${nInDOM(2)})`, @@ -16769,63 +16997,212 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([ + expect(stateVariables["/_circle1"].stateValues.numericalCenter).eqls([ -7, 2, ]); - expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); - expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([ - -7, 2, + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + -7, + 2, + ]); + expect(stateVariables["/_point1"].stateValues.coords).eqls([ + "vector", + 2, + -7, ]); - expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); }); }); - cy.log("change point for center"); - cy.get(cesc("#\\/c") + " textarea").type( - "{end}{leftArrow}{backspace}-4{enter}", - { force: true }, - ); - - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(-7)},${nInDOM(-4)})`, - ); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([ - -7, -4, - ]); - expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); - expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([ - -7, -4, - ]); - expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); - }); - - cy.log(`move circle2`); + cy.log("move flipped point"); cy.window().then(async (win) => { await win.callAction1({ - actionName: "moveCircle", - componentName: "/circ2", - args: { center: [6, 9] }, + actionName: "movePoint", + componentName: "/_point1", + args: { x: -3, y: -5 }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(6)},${nInDOM(9)})`, + `(${nInDOM(-5)},${nInDOM(-3)})`, ); + cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([ - 6, 9, + expect(stateVariables["/_circle1"].stateValues.numericalCenter).eqls([ + -5, -3, ]); - expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); - expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([ - 6, 9, + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + -5, + -3, ]); - expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); - }); - }); + expect(stateVariables["/_point1"].stateValues.coords).eqls([ + "vector", + -3, + -5, + ]); + }); + }); + + cy.log("move center point"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: 1, y: -4 }, + }); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(1)},${nInDOM(-4)})`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.numericalCenter).eqls([ + 1, -4, + ]); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 1, + -4, + ]); + expect(stateVariables["/_point1"].stateValues.coords).eqls([ + "vector", + -4, + 1, + ]); + }); + }); + }); + + it("handle initially undefined center", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a +

Center:

+ + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([ + NaN, + NaN, + ]); + expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); + expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([ + NaN, + NaN, + ]); + expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(_,_)`); + }); + + cy.log("enter point for center"); + cy.get(cesc("#\\/c") + " textarea").type("(2,1){enter}", { force: true }); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(2)},${nInDOM(1)})`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([2, 1]); + expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); + expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([2, 1]); + expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); + }); + + cy.log(`move circle`); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + await win.callAction1({ + actionName: "moveCircle", + componentName: "/circ", + args: { center: [-7, 2] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(-7)},${nInDOM(2)})`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([ + -7, 2, + ]); + expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); + expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([ + -7, 2, + ]); + expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); + }); + }); + + cy.log("change point for center"); + cy.get(cesc("#\\/c") + " textarea").type( + "{end}{leftArrow}{backspace}-4{enter}", + { force: true }, + ); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(-7)},${nInDOM(-4)})`, + ); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([ + -7, -4, + ]); + expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); + expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([ + -7, -4, + ]); + expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); + }); + + cy.log(`move circle2`); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "moveCircle", + componentName: "/circ2", + args: { center: [6, 9] }, + }); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(6)},${nInDOM(9)})`, + ); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([ + 6, 9, + ]); + expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); + expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([ + 6, 9, + ]); + expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); + }); + }); cy.log("center undefined again"); cy.get(cesc("#\\/c") + " textarea").type( @@ -17683,93 +18060,4009 @@ describe("Circle Tag Tests", function () { ); }); - cy.get(cesc("#\\/Adescrip")).should( - "have.text", - "Circle A is filled brown with thick border.", - ); - cy.get(cesc("#\\/Bdescrip")).should( - "have.text", - "B is a filled dark red circle.", - ); - cy.get(cesc("#\\/Cdescrip")).should( - "have.text", - "C is a filled black circle with a thin border.", - ); - cy.get(cesc("#\\/Aborderdescrip")).should( - "have.text", - "A has a thick brown border.", - ); - cy.get(cesc("#\\/Bborderdescrip")).should( - "have.text", - "B has a dark red border.", - ); - cy.get(cesc("#\\/Cborderdescrip")).should( - "have.text", - "C has a thin black border.", - ); - cy.get(cesc("#\\/Afilldescrip")).should("have.text", "A has a brown fill."); - cy.get(cesc("#\\/Bfilldescrip")).should( - "have.text", - "B has a dark red fill.", - ); - cy.get(cesc("#\\/Cfilldescrip")).should("have.text", "C has a black fill."); + cy.get(cesc("#\\/Adescrip")).should( + "have.text", + "Circle A is filled brown with thick border.", + ); + cy.get(cesc("#\\/Bdescrip")).should( + "have.text", + "B is a filled dark red circle.", + ); + cy.get(cesc("#\\/Cdescrip")).should( + "have.text", + "C is a filled black circle with a thin border.", + ); + cy.get(cesc("#\\/Aborderdescrip")).should( + "have.text", + "A has a thick brown border.", + ); + cy.get(cesc("#\\/Bborderdescrip")).should( + "have.text", + "B has a dark red border.", + ); + cy.get(cesc("#\\/Cborderdescrip")).should( + "have.text", + "C has a thin black border.", + ); + cy.get(cesc("#\\/Afilldescrip")).should("have.text", "A has a brown fill."); + cy.get(cesc("#\\/Bfilldescrip")).should( + "have.text", + "B has a dark red fill.", + ); + cy.get(cesc("#\\/Cfilldescrip")).should("have.text", "C has a black fill."); + + cy.log("set dark mode"); + cy.get("#testRunner_toggleControls").click(); + cy.get("#testRunner_darkmode").click(); + cy.wait(100); + cy.get("#testRunner_toggleControls").click(); + + cy.get(cesc("#\\/Adescrip")).should( + "have.text", + "Circle A is filled yellow with thick border.", + ); + cy.get(cesc("#\\/Bdescrip")).should( + "have.text", + "B is a filled light red circle.", + ); + cy.get(cesc("#\\/Cdescrip")).should( + "have.text", + "C is a filled white circle with a thin border.", + ); + cy.get(cesc("#\\/Aborderdescrip")).should( + "have.text", + "A has a thick yellow border.", + ); + cy.get(cesc("#\\/Bborderdescrip")).should( + "have.text", + "B has a light red border.", + ); + cy.get(cesc("#\\/Cborderdescrip")).should( + "have.text", + "C has a thin white border.", + ); + cy.get(cesc("#\\/Afilldescrip")).should( + "have.text", + "A has a yellow fill.", + ); + cy.get(cesc("#\\/Bfilldescrip")).should( + "have.text", + "B has a light red fill.", + ); + cy.get(cesc("#\\/Cfilldescrip")).should("have.text", "C has a white fill."); + }); + + it("circle with center and through point, center constrained", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,4) + + + + + (5,6) + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + let cnx = 3, + cny = 4; + let tx = 5, + ty = 6; + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(cnx, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(cny, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + let cnx = 3, + cny = 4; + let tx = 5, + ty = 6; + + cy.log("move circle"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = -2, + dy = -6; + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + tx += cnx - desiredcnx; + ty += cny - desiredcny; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move defining center"); + cy.window().then(async (win) => { + let desiredcnx = -5; + let desiredcny = 5; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point1", + args: { x: desiredcnx, y: desiredcny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move reffed center"); + cy.window().then(async (win) => { + let desiredcnx = 1; + let desiredcny = -1; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: desiredcnx, y: desiredcny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move through point"); + cy.window().then(async (win) => { + tx = -4; + ty = 3; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point2", + args: { x: tx, y: ty }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("change reffed radius"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + r = r / 4; + + tx = cnx + (tx - cnx) / 4; + ty = cny + (ty - cny) / 4; + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point3", + args: { x: r, y: 0 }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle2"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = 4, + dy = -1; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + tx += cnx - desiredcnx; + ty += cny - desiredcny; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = -5, + dy = 4; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + tx += cnx - desiredcnx; + ty += cny - desiredcny; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + }); + + it("circle with center and through point, center constrained, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,4) + + + + + (5,6) + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + let cnx = 3, + cny = 4; + let tx = 5, + ty = 6; + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(cnx, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(cny, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + let cnx = 3, + cny = 4; + let tx = 5, + ty = 6; + + cy.log("move circle"); + cy.window().then(async (win) => { + let dx = -2, + dy = -6; + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move defining center"); + cy.window().then(async (win) => { + let desiredcnx = -5; + let desiredcny = 5; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point1", + args: { x: desiredcnx, y: desiredcny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move reffed center"); + cy.window().then(async (win) => { + let desiredcnx = 1; + let desiredcny = -1; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: desiredcnx, y: desiredcny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move through point"); + cy.window().then(async (win) => { + tx = -4; + ty = 3; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point2", + args: { x: tx, y: ty }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("change reffed radius"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + r = r / 4; + + tx = cnx + (tx - cnx) / 4; + ty = cny + (ty - cny) / 4; + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point3", + args: { x: r, y: 0 }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle2"); + cy.window().then(async (win) => { + let dx = 4, + dy = -1; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let dx = -5, + dy = 4; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + }); + + it("circle with center and through point, through point constrained", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,4) + + (5,7) + + + + + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + let cnx = 3, + cny = 4; + let tx = 6, + ty = 8; + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(cnx, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(cny, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + let cnx = 3, + cny = 4; + let tx = 6, + ty = 8; + + cy.log("move circle"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = -2, + dy = -6; + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + cnx = Math.round(desiredcnx / 3) * 3; + cny = Math.round(desiredcny / 2) * 2; + + tx += cnx - desiredcnx; + ty += cny - desiredcny; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move defining center"); + cy.window().then(async (win) => { + cnx = -5; + cny = 5; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point1", + args: { x: cnx, y: cny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move reffed center"); + cy.window().then(async (win) => { + cnx = 1; + cny = -1; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: cnx, y: cny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move through point"); + cy.window().then(async (win) => { + let desiredtx = -4; + let desiredty = 3; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point2", + args: { x: desiredtx, y: desiredty }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("change reffed radius"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let desiredr = r / 4; + + let desiredtx = cnx + (tx - cnx) / 4; + let desiredty = cny + (ty - cny) / 4; + + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point3", + args: { x: desiredr, y: 0 }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle2"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = 4, + dy = -1; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + + let desiredtx = tx; + let desiredty = ty; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + cnx += tx - desiredtx; + cny += ty - desiredty; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let dx = -5, + dy = 4; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredcnx = cnx; + let desiredcny = cny; + + let desiredtx = tx; + let desiredty = ty; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + cnx += tx - desiredtx; + cny += ty - desiredty; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + }); + + it("circle with center and through point, through point constrained, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,4) + + (5,7) + + + + + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + let cnx = 3, + cny = 4; + let tx = 6, + ty = 8; + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(cnx, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(cny, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + let cnx = 3, + cny = 4; + let tx = 6, + ty = 8; + + cy.log("move circle"); + cy.window().then(async (win) => { + let dx = -2, + dy = -6; + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredtx = tx; + let desiredty = ty; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [cnx, cny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move defining center"); + cy.window().then(async (win) => { + cnx = -5; + cny = 5; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point1", + args: { x: cnx, y: cny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move reffed center"); + cy.window().then(async (win) => { + cnx = 1; + cny = -1; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: cnx, y: cny }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move through point"); + cy.window().then(async (win) => { + let desiredtx = -4; + let desiredty = 3; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point2", + args: { x: desiredtx, y: desiredty }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("change reffed radius"); + cy.window().then(async (win) => { + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + let desiredr = r / 4; + + let desiredtx = cnx + (tx - cnx) / 4; + let desiredty = cny + (ty - cny) / 4; + + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point3", + args: { x: desiredr, y: 0 }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle2"); + cy.window().then(async (win) => { + let dx = 4, + dy = -1; + + cnx += dx; + cny += dy; + tx += dx; + ty += dy; + + let desiredtx = tx; + let desiredty = ty; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [cnx, cny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let dx = -5, + dy = 4; - cy.log("set dark mode"); - cy.get("#testRunner_toggleControls").click(); - cy.get("#testRunner_darkmode").click(); - cy.wait(100); - cy.get("#testRunner_toggleControls").click(); + cnx += dx; + cny += dy; + tx += dx; + ty += dy; - cy.get(cesc("#\\/Adescrip")).should( - "have.text", - "Circle A is filled yellow with thick border.", - ); - cy.get(cesc("#\\/Bdescrip")).should( - "have.text", - "B is a filled light red circle.", - ); - cy.get(cesc("#\\/Cdescrip")).should( - "have.text", - "C is a filled white circle with a thin border.", - ); - cy.get(cesc("#\\/Aborderdescrip")).should( - "have.text", - "A has a thick yellow border.", - ); - cy.get(cesc("#\\/Bborderdescrip")).should( - "have.text", - "B has a light red border.", - ); - cy.get(cesc("#\\/Cborderdescrip")).should( - "have.text", - "C has a thin white border.", - ); - cy.get(cesc("#\\/Afilldescrip")).should( - "have.text", - "A has a yellow fill.", - ); - cy.get(cesc("#\\/Bfilldescrip")).should( - "have.text", - "B has a light red fill.", - ); - cy.get(cesc("#\\/Cfilldescrip")).should("have.text", "C has a white fill."); + let desiredtx = tx; + let desiredty = ty; + tx = Math.round(desiredtx / 3) * 3; + ty = Math.round(desiredty / 2) * 2; + + let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [cnx, cny] }, + }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + await stateVariables["/graph4/circle"].stateValues.radius, + ).closeTo(r, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect( + (await stateVariables["/centerPoint"].stateValues.xs)[0], + ).closeTo(cnx, 1e-12); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + }); + }); }); - it("circle with center and through point, center constrained", () => { + it("circle through two points, one point constrained", () => { cy.window().then(async (win) => { win.postMessage( { doenetML: ` a - (3,4) + + (2,-3) - (5,6) - - + (3,4) + @@ -17791,14 +22084,16 @@ describe("Circle Tag Tests", function () { cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + let t1x = 3, + t1y = -2; + let t2x = 3, + t2y = 4; cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - let cnx = 3, - cny = 4; - let tx = 5, - ty = 6; - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, @@ -17860,10 +22155,10 @@ describe("Circle Tag Tests", function () { expect( stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); - expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(cnx, 1e-12); - expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(cny, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(t1x, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(t1y, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(t2x, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(t2y, 1e-12); expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( cnx, 1e-12, @@ -17878,11 +22173,7 @@ describe("Circle Tag Tests", function () { ); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -17890,29 +22181,31 @@ describe("Circle Tag Tests", function () { ); }); - let cnx = 3, - cny = 4; - let tx = 5, - ty = 6; - cy.log("move circle"); cy.window().then(async (win) => { - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + let dx = -2, + dy = -7; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let desiredcnx = (t1x + t2x) / 2; + let desiredcny = (t1y + t2y) / 2; + + let desiredt1x = t1x; + let desiredt1y = t1y; - let dx = -2, - dy = -6; - cnx += dx; - cny += dy; - tx += dx; - ty += dy; + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; - let desiredcnx = cnx; - let desiredcny = cny; - cnx = Math.round(desiredcnx / 3) * 3; - cny = Math.round(desiredcny / 2) * 2; + t2x += t1x - desiredt1x; + t2y += t1y - desiredt1y; - tx += cnx - desiredcnx; - ty += cny - desiredcny; + let cnx = (t1x + t2x) / 2; + let cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "moveCircle", @@ -17921,11 +22214,7 @@ describe("Circle Tag Tests", function () { }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -17934,7 +22223,6 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, @@ -17998,15 +22286,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -18021,27 +22315,25 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("move defining center"); + cy.log("move first through point"); cy.window().then(async (win) => { - let desiredcnx = -5; - let desiredcny = 5; - cnx = Math.round(desiredcnx / 3) * 3; - cny = Math.round(desiredcny / 2) * 2; + let desiredt1x = 4, + desiredt1y = -1; - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "movePoint", componentName: "/_point1", - args: { x: desiredcnx, y: desiredcny }, + args: { x: desiredt1x, y: desiredt1y }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -18050,7 +22342,6 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, @@ -18114,15 +22405,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -18137,27 +22434,21 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("move reffed center"); + cy.log("move second through point"); cy.window().then(async (win) => { - let desiredcnx = 1; - let desiredcny = -1; - cnx = Math.round(desiredcnx / 3) * 3; - cny = Math.round(desiredcny / 2) * 2; - - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); - + t2x = 8; + t2y = -3; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "movePoint", - componentName: "/centerPoint", - args: { x: desiredcnx, y: desiredcny }, + componentName: "/_point2", + args: { x: t2x, y: t2y }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -18166,7 +22457,6 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, @@ -18230,15 +22520,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -18253,25 +22549,32 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("move through point"); + cy.log("move center"); cy.window().then(async (win) => { - tx = -4; - ty = 3; + let dx = 2, + dy = -3; - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + let desiredcnx = (t1x + t2x) / 2 + dx; + let desiredcny = (t1y + t2y) / 2 + dy; + + t1x = Math.round((t1x + dx) / 3) * 3; + t1y = Math.round((t1y + dy) / 2) * 2; + + t2x += dx; + t2y += dy; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "movePoint", - componentName: "/_point2", - args: { x: tx, y: ty }, + componentName: "/centerPoint", + args: { x: desiredcnx, y: desiredcny }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -18280,7 +22583,6 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, @@ -18344,15 +22646,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -18367,27 +22675,36 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("change reffed radius"); + cy.log("move radius to half size"); cy.window().then(async (win) => { - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + let desiredr = + Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 4; - r = r / 4; + let desiredcnx = (t1x + t2x) / 2; + let desiredcny = (t1y + t2y) / 2; - tx = cnx + (tx - cnx) / 4; - ty = cny + (ty - cny) / 4; + let desiredt1x = desiredcnx + (t1x - desiredcnx) / 2; + let desiredt1y = desiredcny + (t1y - desiredcny) / 2; + + t2x = desiredcnx + (t2x - desiredcnx) / 2; + t2y = desiredcny + (t2y - desiredcny) / 2; + + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "movePoint", componentName: "/_point3", - args: { x: r, y: 0 }, + args: { x: desiredr, y: 0 }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -18459,15 +22776,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -18484,23 +22807,30 @@ describe("Circle Tag Tests", function () { cy.log("move circle2"); cy.window().then(async (win) => { - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + let dx = -8; + let dy = 5; - let dx = 4, - dy = -1; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; - cnx += dx; - cny += dy; - tx += dx; - ty += dy; + let desiredt1x = t1x; + let desiredt1y = t1y; - let desiredcnx = cnx; - let desiredcny = cny; - cnx = Math.round(desiredcnx / 3) * 3; - cny = Math.round(desiredcny / 2) * 2; + let desiredcnx = (desiredt1x + t2x) / 2; + let desiredcny = (desiredt1y + t2y) / 2; - tx += cnx - desiredcnx; - ty += cny - desiredcny; + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + + t2x += t1x - desiredt1x; + t2y += t1y - desiredt1y; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "moveCircle", @@ -18509,11 +22839,7 @@ describe("Circle Tag Tests", function () { }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -18585,15 +22911,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -18610,23 +22942,30 @@ describe("Circle Tag Tests", function () { cy.log("move circle3"); cy.window().then(async (win) => { - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + let dx = -3; + let dy = 3; - let dx = -5, - dy = 4; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; - cnx += dx; - cny += dy; - tx += dx; - ty += dy; + let desiredt1x = t1x; + let desiredt1y = t1y; - let desiredcnx = cnx; - let desiredcny = cny; - cnx = Math.round(desiredcnx / 3) * 3; - cny = Math.round(desiredcny / 2) * 2; + let desiredcnx = (desiredt1x + t2x) / 2; + let desiredcny = (desiredt1y + t2y) / 2; - tx += cnx - desiredcnx; - ty += cny - desiredcny; + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + + t2x += t1x - desiredt1x; + t2y += t1y - desiredt1y; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "moveCircle", @@ -18635,11 +22974,7 @@ describe("Circle Tag Tests", function () { }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -18711,15 +23046,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -18735,22 +23076,21 @@ describe("Circle Tag Tests", function () { }); }); - it("circle with center and through point, through point constrained", () => { + it("circle through two points, one point constrained, allow flexible motion", () => { cy.window().then(async (win) => { win.postMessage( { doenetML: ` a - (3,4) - - (5,7) + + (2,-3) - - + (3,4) + @@ -18772,14 +23112,16 @@ describe("Circle Tag Tests", function () { cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + let t1x = 3, + t1y = -2; + let t2x = 3, + t2y = 4; cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - let cnx = 3, - cny = 4; - let tx = 6, - ty = 8; - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, @@ -18836,305 +23178,68 @@ describe("Circle Tag Tests", function () { ).closeTo(cny, 1e-12); expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( r, - 1e-12, - ); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(cnx, 1e-12); - expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(cny, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); - expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, - 1e-12, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, - ); - cy.get(cesc("#\\/radiusNumber")).should( - "contain.text", - nInDOM(Math.trunc(r * 100) / 100), - ); - }); - - let cnx = 3, - cny = 4; - let tx = 6, - ty = 8; - - cy.log("move circle"); - cy.window().then(async (win) => { - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); - - let dx = -2, - dy = -6; - cnx += dx; - cny += dy; - tx += dx; - ty += dy; - - let desiredcnx = cnx; - let desiredcny = cny; - cnx = Math.round(desiredcnx / 3) * 3; - cny = Math.round(desiredcny / 2) * 2; - - tx += cnx - desiredcnx; - ty += cny - desiredcny; - - await win.callAction1({ - actionName: "moveCircle", - componentName: "/_circle1", - args: { center: [desiredcnx, desiredcny] }, - }); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, - ); - cy.get(cesc("#\\/radiusNumber")).should( - "contain.text", - nInDOM(Math.trunc(r * 100) / 100), - ); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph4/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); - expect( - (await stateVariables["/centerPoint"].stateValues.xs)[0], - ).closeTo(cnx, 1e-12); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, - 1e-12, - ); - }); - }); - - cy.log("move defining center"); - cy.window().then(async (win) => { - cnx = -5; - cny = 5; - - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); - - await win.callAction1({ - actionName: "movePoint", - componentName: "/_point1", - args: { x: cnx, y: cny }, - }); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(t1x, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(t1y, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(t2x, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(t2y, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, ); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", nInDOM(Math.trunc(r * 100) / 100), ); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph4/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); - expect( - (await stateVariables["/centerPoint"].stateValues.xs)[0], - ).closeTo(cnx, 1e-12); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, - 1e-12, - ); - }); }); - cy.log("move reffed center"); + cy.log("move circle"); cy.window().then(async (win) => { - cnx = 1; - cny = -1; + let dx = -2, + dy = -7; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + let desiredcnx = (t1x + t2x) / 2; + let desiredcny = (t1y + t2y) / 2; + + let desiredt1x = t1x; + let desiredt1y = t1y; + + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + + let cnx = (t1x + t2x) / 2; + let cny = (t1y + t2y) / 2; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; await win.callAction1({ - actionName: "movePoint", - componentName: "/centerPoint", - args: { x: cnx, y: cny }, + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -19143,7 +23248,6 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, @@ -19207,15 +23311,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -19230,27 +23340,25 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("move through point"); + cy.log("move first through point"); cy.window().then(async (win) => { - let desiredtx = -4; - let desiredty = 3; - tx = Math.round(desiredtx / 3) * 3; - ty = Math.round(desiredty / 2) * 2; + let desiredt1x = 4, + desiredt1y = -1; - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "movePoint", - componentName: "/_point2", - args: { x: desiredtx, y: desiredty }, + componentName: "/_point1", + args: { x: desiredt1x, y: desiredt1y }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -19259,7 +23367,6 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, @@ -19323,15 +23430,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -19346,32 +23459,21 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("change reffed radius"); + cy.log("move second through point"); cy.window().then(async (win) => { - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); - - let desiredr = r / 4; - - let desiredtx = cnx + (tx - cnx) / 4; - let desiredty = cny + (ty - cny) / 4; - - tx = Math.round(desiredtx / 3) * 3; - ty = Math.round(desiredty / 2) * 2; - - r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); - + t2x = 8; + t2y = -3; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "movePoint", - componentName: "/_point3", - args: { x: desiredr, y: 0 }, + componentName: "/_point2", + args: { x: t2x, y: t2y }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -19443,15 +23545,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -19466,41 +23574,32 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("move circle2"); + cy.log("move center"); cy.window().then(async (win) => { - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); - - let dx = 4, - dy = -1; - - cnx += dx; - cny += dy; - tx += dx; - ty += dy; + let dx = 2, + dy = -3; - let desiredcnx = cnx; - let desiredcny = cny; + let desiredcnx = (t1x + t2x) / 2 + dx; + let desiredcny = (t1y + t2y) / 2 + dy; - let desiredtx = tx; - let desiredty = ty; - tx = Math.round(desiredtx / 3) * 3; - ty = Math.round(desiredty / 2) * 2; + t1x = Math.round((t1x + dx) / 3) * 3; + t1y = Math.round((t1y + dy) / 2) * 2; - cnx += tx - desiredtx; - cny += ty - desiredty; + t2x += dx; + t2y += dy; + + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ - actionName: "moveCircle", - componentName: "/graph3/circle", - args: { center: [desiredcnx, desiredcny] }, + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: desiredcnx, y: desiredcny }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -19572,15 +23671,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -19595,41 +23700,36 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("move circle3"); + cy.log("move radius to half size"); cy.window().then(async (win) => { - let r = Math.sqrt(Math.pow(tx - cnx, 2) + Math.pow(ty - cny, 2)); + let desiredr = + Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 4; - let dx = -5, - dy = 4; + let desiredcnx = (t1x + t2x) / 2; + let desiredcny = (t1y + t2y) / 2; - cnx += dx; - cny += dy; - tx += dx; - ty += dy; + let desiredt1x = desiredcnx + (t1x - desiredcnx) / 2; + let desiredt1y = desiredcny + (t1y - desiredcny) / 2; - let desiredcnx = cnx; - let desiredcny = cny; + t2x = desiredcnx + (t2x - desiredcnx) / 2; + t2y = desiredcny + (t2y - desiredcny) / 2; - let desiredtx = tx; - let desiredty = ty; - tx = Math.round(desiredtx / 3) * 3; - ty = Math.round(desiredty / 2) * 2; + t1x = Math.round(desiredt1x / 3) * 3; + t1y = Math.round(desiredt1y / 2) * 2; - cnx += tx - desiredtx; - cny += ty - desiredty; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ - actionName: "moveCircle", - componentName: "/graph4/circle", - args: { center: [desiredcnx, desiredcny] }, + actionName: "movePoint", + componentName: "/_point3", + args: { x: desiredr, y: 0 }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `(${nInDOM(cnx)},${nInDOM(cny)})`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -19701,15 +23801,21 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( - cnx, + t1x, 1e-12, ); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( - cny, + t1y, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, + 1e-12, + ); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(tx, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(ty, 1e-12); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -19723,170 +23829,34 @@ describe("Circle Tag Tests", function () { ); }); }); - }); - - it("circle through two points, one point constrained", () => { - cy.window().then(async (win) => { - win.postMessage( - { - doenetML: ` - a - - - (2,-3) - - - - - (3,4) - - - - - - - - - - - - - - - `, - }, - "*", - ); - }); - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load - - let t1x = 3, - t1y = -2; - let t2x = 3, - t2y = 4; + cy.log("move circle2"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - let cnx = (t1x + t2x) / 2, - cny = (t1y + t2y) / 2; - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); - expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( - r, - 1e-12, - ); - expect( - stateVariables["/graph3/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( - r, - 1e-12, - ); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(t1x, 1e-12); - expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(t1y, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(t2x, 1e-12); - expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(t2y, 1e-12); - expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, - 1e-12, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(cnx)},${nInDOM(cny)})`, - ); - cy.get(cesc("#\\/radiusNumber")).should( - "contain.text", - nInDOM(Math.trunc(r * 100) / 100), - ); - }); + let dx = -8; + let dy = 5; - cy.log("move circle"); - cy.window().then(async (win) => { - let dx = -2, - dy = -7; t1x += dx; t1y += dy; t2x += dx; t2y += dy; - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - - let desiredcnx = (t1x + t2x) / 2; - let desiredcny = (t1y + t2y) / 2; - let desiredt1x = t1x; let desiredt1y = t1y; + let desiredcnx = (desiredt1x + t2x) / 2; + let desiredcny = (desiredt1y + t2y) / 2; + t1x = Math.round(desiredt1x / 3) * 3; t1y = Math.round(desiredt1y / 2) * 2; - t2x += t1x - desiredt1x; - t2y += t1y - desiredt1y; + let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - let cnx = (t1x + t2x) / 2; - let cny = (t1y + t2y) / 2; + let cnx = (t1x + t2x) / 2, + cny = (t1y + t2y) / 2; await win.callAction1({ actionName: "moveCircle", - componentName: "/_circle1", + componentName: "/graph3/circle", args: { center: [desiredcnx, desiredcny] }, }); cy.get(cesc("#\\/centerPoint2")).should( @@ -19992,21 +23962,34 @@ describe("Circle Tag Tests", function () { }); }); - cy.log("move first through point"); + cy.log("move circle3"); cy.window().then(async (win) => { - let desiredt1x = 4, - desiredt1y = -1; + let dx = -3; + let dy = 3; + + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + + let desiredt1x = t1x; + let desiredt1y = t1y; + + let desiredcnx = (desiredt1x + t2x) / 2; + let desiredcny = (desiredt1y + t2y) / 2; t1x = Math.round(desiredt1x / 3) * 3; t1y = Math.round(desiredt1y / 2) * 2; let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let cnx = (t1x + t2x) / 2, cny = (t1y + t2y) / 2; + await win.callAction1({ - actionName: "movePoint", - componentName: "/_point1", - args: { x: desiredt1x, y: desiredt1y }, + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", @@ -20110,22 +24093,184 @@ describe("Circle Tag Tests", function () { ); }); }); + }); - cy.log("move second through point"); + it("circle through three points, one point constrained", () => { cy.window().then(async (win) => { - t2x = 8; - t2y = -3; - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - let cnx = (t1x + t2x) / 2, - cny = (t1y + t2y) / 2; + win.postMessage( + { + doenetML: ` + a + + (2,-3) + (3,4) + + + + + (-3,4) + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + let t1x = 2, + t1y = -3; + let t2x = 3, + t2y = 4; + let t3x = -3, + t3y = 4; + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/_circle1"].stateValues.numericalCenter[0]; + let cny = stateVariables["/_circle1"].stateValues.numericalCenter[1]; + let r = stateVariables["/_circle1"].stateValues.numericalRadius; + + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); + + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph3/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[0], + ).closeTo(cnx, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalCenter[1], + ).closeTo(cny, 1e-12); + expect( + stateVariables["/graph4/circle"].stateValues.numericalRadius, + ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); + expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( + r, + 1e-12, + ); + + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(t1x, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(t1y, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(t2x, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(t2y, 1e-12); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo(t3x, 1e-12); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo(t3y, 1e-12); + expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( + cny, + 1e-12, + ); + expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( + r, + 1e-12, + ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, + ); + cy.get(cesc("#\\/radiusNumber")).should( + "contain.text", + nInDOM(Math.trunc(r * 100) / 100), + ); + }); + + cy.log("move circle up and to the right"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/_circle1"].stateValues.numericalCenter[0]; + let cny = stateVariables["/_circle1"].stateValues.numericalCenter[1]; + let r = stateVariables["/_circle1"].stateValues.numericalRadius; + + let desireddx = 5, + desireddy = 3; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = 6; + let dy = 4; + + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; + await win.callAction1({ - actionName: "movePoint", - componentName: "/_point2", - args: { x: t2x, y: t2y }, + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, }); cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(cnx)},${nInDOM(cny)})`, + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -20134,56 +24279,48 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); expect( stateVariables["/_circle1"].stateValues.numericalCenter[0], ).closeTo(cnx, 1e-12); expect( stateVariables["/_circle1"].stateValues.numericalCenter[1], ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( r, 1e-12, ); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( cny, 1e-12, ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); expect( stateVariables["/graph3/circle"].stateValues.numericalCenter[0], ).closeTo(cnx, 1e-12); expect( stateVariables["/graph3/circle"].stateValues.numericalCenter[1], ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); expect( stateVariables["/graph3/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( cnx, 1e-12, ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( cny, 1e-12, ); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); expect( stateVariables["/graph4/circle"].stateValues.numericalCenter[0], ).closeTo(cnx, 1e-12); @@ -20191,10 +24328,18 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalCenter[1], ).closeTo(cny, 1e-12); expect( - await stateVariables["/graph4/circle"].stateValues.radius, + stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, + await stateVariables["/graph4/circle"].stateValues.radius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( t1x, @@ -20212,6 +24357,14 @@ describe("Circle Tag Tests", function () { t2y, 1e-12, ); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, + 1e-12, + ); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -20223,35 +24376,49 @@ describe("Circle Tag Tests", function () { r, 1e-12, ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); }); }); - cy.log("move center"); + cy.log("move circle2"); cy.window().then(async (win) => { - let dx = 2, - dy = -3; + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/graph3/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph3/circle"].stateValues.numericalCenter[1]; + let r = stateVariables["/graph3/circle"].stateValues.numericalRadius; - let desiredcnx = (t1x + t2x) / 2 + dx; - let desiredcny = (t1y + t2y) / 2 + dy; + let desireddx = -5, + desireddy = -2.2; - t1x = Math.round((t1x + dx) / 3) * 3; - t1y = Math.round((t1y + dy) / 2) * 2; + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + let dx = -6; + let dy = -2; + + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; t2x += dx; t2y += dy; + t3x += dx; + t3y += dy; - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; - - let cnx = (t1x + t2x) / 2, - cny = (t1y + t2y) / 2; await win.callAction1({ - actionName: "movePoint", - componentName: "/centerPoint", - args: { x: desiredcnx, y: desiredcny }, + actionName: "moveCircle", + componentName: "/graph3/circle", + args: { center: [desiredcnx, desiredcny] }, }); + cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(cnx)},${nInDOM(cny)})`, + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -20260,56 +24427,49 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); + expect( stateVariables["/_circle1"].stateValues.numericalCenter[0], ).closeTo(cnx, 1e-12); expect( stateVariables["/_circle1"].stateValues.numericalCenter[1], ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( r, 1e-12, ); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( cny, 1e-12, ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); expect( stateVariables["/graph3/circle"].stateValues.numericalCenter[0], ).closeTo(cnx, 1e-12); expect( stateVariables["/graph3/circle"].stateValues.numericalCenter[1], ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); expect( stateVariables["/graph3/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( cnx, 1e-12, ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( cny, 1e-12, ); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); expect( stateVariables["/graph4/circle"].stateValues.numericalCenter[0], ).closeTo(cnx, 1e-12); @@ -20317,10 +24477,18 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalCenter[1], ).closeTo(cny, 1e-12); expect( - await stateVariables["/graph4/circle"].stateValues.radius, + stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, + await stateVariables["/graph4/circle"].stateValues.radius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( t1x, @@ -20338,6 +24506,14 @@ describe("Circle Tag Tests", function () { t2y, 1e-12, ); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, + 1e-12, + ); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -20349,39 +24525,52 @@ describe("Circle Tag Tests", function () { r, 1e-12, ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); }); }); - cy.log("move radius to half size"); + cy.log("move circle3"); cy.window().then(async (win) => { - let desiredr = - Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 4; - - let desiredcnx = (t1x + t2x) / 2; - let desiredcny = (t1y + t2y) / 2; + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/graph4/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph4/circle"].stateValues.numericalCenter[1]; + let r = stateVariables["/graph4/circle"].stateValues.numericalRadius; - let desiredt1x = desiredcnx + (t1x - desiredcnx) / 2; - let desiredt1y = desiredcny + (t1y - desiredcny) / 2; + let desireddx = 7, + desireddy = -3; - t2x = desiredcnx + (t2x - desiredcnx) / 2; - t2y = desiredcny + (t2y - desiredcny) / 2; + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; - t1x = Math.round(desiredt1x / 3) * 3; - t1y = Math.round(desiredt1y / 2) * 2; + let dx = 6; + let dy = -2; - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; - let cnx = (t1x + t2x) / 2, - cny = (t1y + t2y) / 2; + cnx = Math.round(cnx * 1e14) / 1e14; + cny = Math.round(cny * 1e14) / 1e14; await win.callAction1({ - actionName: "movePoint", - componentName: "/_point3", - args: { x: desiredr, y: 0 }, + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, }); + cy.get(cesc("#\\/centerPoint2")).should( "contain.text", - `(${nInDOM(cnx)},${nInDOM(cny)})`, + `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `${nInDOM(Math.trunc(cny * 100) / 100)}`, ); cy.get(cesc("#\\/radiusNumber")).should( "contain.text", @@ -20390,56 +24579,49 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); + expect( stateVariables["/_circle1"].stateValues.numericalCenter[0], ).closeTo(cnx, 1e-12); expect( stateVariables["/_circle1"].stateValues.numericalCenter[1], ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( r, 1e-12, ); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( + expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( cnx, 1e-12, ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( + expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( cny, 1e-12, ); + expect(stateVariables["/_circle1"].stateValues.radius).closeTo( + r, + 1e-12, + ); expect( stateVariables["/graph3/circle"].stateValues.numericalCenter[0], ).closeTo(cnx, 1e-12); expect( stateVariables["/graph3/circle"].stateValues.numericalCenter[1], ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); expect( stateVariables["/graph3/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( cnx, 1e-12, ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( cny, 1e-12, ); + expect( + await stateVariables["/graph3/circle"].stateValues.radius, + ).closeTo(r, 1e-12); expect( stateVariables["/graph4/circle"].stateValues.numericalCenter[0], ).closeTo(cnx, 1e-12); @@ -20447,10 +24629,18 @@ describe("Circle Tag Tests", function () { stateVariables["/graph4/circle"].stateValues.numericalCenter[1], ).closeTo(cny, 1e-12); expect( - await stateVariables["/graph4/circle"].stateValues.radius, + stateVariables["/graph4/circle"].stateValues.numericalRadius, ).closeTo(r, 1e-12); + expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( + cnx, + 1e-12, + ); + expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( + cny, + 1e-12, + ); expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, + await stateVariables["/graph4/circle"].stateValues.radius, ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( t1x, @@ -20468,6 +24658,14 @@ describe("Circle Tag Tests", function () { t2y, 1e-12, ); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, + 1e-12, + ); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, + 1e-12, + ); expect( (await stateVariables["/centerPoint"].stateValues.xs)[0], ).closeTo(cnx, 1e-12); @@ -20479,114 +24677,142 @@ describe("Circle Tag Tests", function () { r, 1e-12, ); + expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); }); }); + }); + + it("circle through three points, one point constrained, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (2,-3) + (3,4) + + + + + (-3,4) + + + + + + + + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + let t1x = 2, + t1y = -3; + let t2x = 3, + t2y = 4; + let t3x = -3, + t3y = 4; + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(t1x, 1e-12); + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(t1y, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo(t2x, 1e-12); + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(t2y, 1e-12); + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo(t3x, 1e-12); + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo(t3y, 1e-12); + + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t1y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t2x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t2y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t3x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t3y * 100) / 100)}`, + ); + }); - cy.log("move circle2"); + cy.log("move circle up and to the right"); cy.window().then(async (win) => { - let dx = -8; - let dy = 5; - - t1x += dx; - t1y += dy; - t2x += dx; - t2y += dy; - - let desiredt1x = t1x; - let desiredt1y = t1y; - - let desiredcnx = (desiredt1x + t2x) / 2; - let desiredcny = (desiredt1y + t2y) / 2; + let stateVariables = await win.returnAllStateVariables1(); + let cnx = stateVariables["/graph3/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph3/circle"].stateValues.numericalCenter[1]; - t1x = Math.round(desiredt1x / 3) * 3; - t1y = Math.round(desiredt1y / 2) * 2; + let desireddx = 5, + desireddy = 3; - t2x += t1x - desiredt1x; - t2y += t1y - desiredt1y; + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let dx = 6; + let dy = 4; - let cnx = (t1x + t2x) / 2, - cny = (t1y + t2y) / 2; + t1x += desireddx; + t1y += desireddy; + t2x += dx; + t2y += dy; + t3x += desireddx; + t3y += desireddy; await win.callAction1({ actionName: "moveCircle", - componentName: "/graph3/circle", + componentName: "/_circle1", args: { center: [desiredcnx, desiredcny] }, }); - cy.get(cesc("#\\/centerPoint2")).should( + + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( "contain.text", - `(${nInDOM(cnx)},${nInDOM(cny)})`, + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, ); - cy.get(cesc("#\\/radiusNumber")).should( + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( "contain.text", - nInDOM(Math.trunc(r * 100) / 100), + `${nInDOM(Math.trunc(t1y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t2x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t2y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t3x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t3y * 100) / 100)}`, ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph4/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( t1x, 1e-12, @@ -20603,125 +24829,163 @@ describe("Circle Tag Tests", function () { t2y, 1e-12, ); - expect( - (await stateVariables["/centerPoint"].stateValues.xs)[0], - ).closeTo(cnx, 1e-12); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, 1e-12, ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, 1e-12, ); }); }); - cy.log("move circle3"); + cy.log("move circle2"); cy.window().then(async (win) => { - let dx = -3; - let dy = 3; - - t1x += dx; - t1y += dy; - t2x += dx; - t2y += dy; - - let desiredt1x = t1x; - let desiredt1y = t1y; - - let desiredcnx = (desiredt1x + t2x) / 2; - let desiredcny = (desiredt1y + t2y) / 2; + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/graph3/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph3/circle"].stateValues.numericalCenter[1]; - t1x = Math.round(desiredt1x / 3) * 3; - t1y = Math.round(desiredt1y / 2) * 2; + let desireddx = -5, + desireddy = -2.2; - t2x += t1x - desiredt1x; - t2y += t1y - desiredt1y; + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let dx = -6; + let dy = -2; - let cnx = (t1x + t2x) / 2, - cny = (t1y + t2y) / 2; + t1x += desireddx; + t1y += desireddy; + t2x += dx; + t2y += dy; + t3x += desireddx; + t3y += desireddy; await win.callAction1({ actionName: "moveCircle", - componentName: "/graph4/circle", + componentName: "/graph3/circle", args: { center: [desiredcnx, desiredcny] }, }); - cy.get(cesc("#\\/centerPoint2")).should( + + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( "contain.text", - `(${nInDOM(cnx)},${nInDOM(cny)})`, + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, ); - cy.get(cesc("#\\/radiusNumber")).should( + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( "contain.text", - nInDOM(Math.trunc(r * 100) / 100), + `${nInDOM(Math.trunc(t1y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t2x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t2y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t3x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t3y * 100) / 100)}`, ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( + t1x, 1e-12, ); - expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( - r, + expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo( + t1y, 1e-12, ); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( - cnx, + expect(stateVariables["/_point2"].stateValues.xs[0]).closeTo( + t2x, 1e-12, ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( - cny, + expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo( + t2y, 1e-12, ); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( - cnx, + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, 1e-12, ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( - cny, + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, 1e-12, ); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - await stateVariables["/graph4/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); + }); + }); + + cy.log("move circle3"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/graph4/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph4/circle"].stateValues.numericalCenter[1]; + + let desireddx = 7, + desireddy = -3; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = 6; + let dy = -2; + + t1x += desireddx; + t1y += desireddy; + t2x += dx; + t2y += dy; + t3x += desireddx; + t3y += desireddy; + + t1x = Math.round(t1x * 1e14) / 1e14; + t1y = Math.round(t1y * 1e14) / 1e14; + t2x = Math.round(t2x * 1e14) / 1e14; + t2y = Math.round(t2y * 1e14) / 1e14; + t3x = Math.round(t3x * 1e14) / 1e14; + t3y = Math.round(t3y * 1e14) / 1e14; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t1y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t2x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t2y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t3x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t3y * 100) / 100)}`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( t1x, 1e-12, @@ -20738,22 +25002,19 @@ describe("Circle Tag Tests", function () { t2y, 1e-12, ); - expect( - (await stateVariables["/centerPoint"].stateValues.xs)[0], - ).closeTo(cnx, 1e-12); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, + expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo( + t3x, 1e-12, ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, + expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo( + t3y, 1e-12, ); }); }); }); - it("circle through three points, one point constrained", () => { + it("circle through three points, two points constrained", () => { cy.window().then(async (win) => { win.postMessage( { @@ -20763,10 +25024,14 @@ describe("Circle Tag Tests", function () { (2,-3) (3,4) - + + + + (-3,4) + + - (-3,4) @@ -21046,7 +25311,7 @@ describe("Circle Tag Tests", function () { let cny = stateVariables["/graph3/circle"].stateValues.numericalCenter[1]; let r = stateVariables["/graph3/circle"].stateValues.numericalRadius; - let desireddx = -5, + let desireddx = -4.9, desireddy = -2.2; let desiredcnx = cnx + desireddx; @@ -21195,8 +25460,8 @@ describe("Circle Tag Tests", function () { let cny = stateVariables["/graph4/circle"].stateValues.numericalCenter[1]; let r = stateVariables["/graph4/circle"].stateValues.numericalRadius; - let desireddx = 7, - desireddy = -3; + let desireddx = 7.1, + desireddy = -2.9; let desiredcnx = cnx + desireddx; let desiredcny = cny + desireddy; @@ -21340,7 +25605,7 @@ describe("Circle Tag Tests", function () { }); }); - it("circle through three points, two points constrained", () => { + it("circle through three points, two points constrained, allow flexible motion", () => { cy.window().then(async (win) => { win.postMessage( { @@ -21358,17 +25623,13 @@ describe("Circle Tag Tests", function () { - + - - - - - + @@ -21390,63 +25651,6 @@ describe("Circle Tag Tests", function () { cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - // calculate center and radius from circle itself - let cnx = stateVariables["/_circle1"].stateValues.numericalCenter[0]; - let cny = stateVariables["/_circle1"].stateValues.numericalCenter[1]; - let r = stateVariables["/_circle1"].stateValues.numericalRadius; - - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo(r, 1e-12); - - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect(await stateVariables["/graph3/circle"].stateValues.radius).closeTo( - r, - 1e-12, - ); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect(await stateVariables["/graph4/circle"].stateValues.radius).closeTo( - r, - 1e-12, - ); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo(t1x, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[1]).closeTo(t1y, 1e-12); @@ -21454,142 +25658,89 @@ describe("Circle Tag Tests", function () { expect(stateVariables["/_point2"].stateValues.xs[1]).closeTo(t2y, 1e-12); expect(stateVariables["/_point3"].stateValues.xs[0]).closeTo(t3x, 1e-12); expect(stateVariables["/_point3"].stateValues.xs[1]).closeTo(t3y, 1e-12); - expect((await stateVariables["/centerPoint"].stateValues.xs)[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); - - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, - ); - cy.get(cesc("#\\/radiusNumber")).should( - "contain.text", - nInDOM(Math.trunc(r * 100) / 100), - ); - }); - - cy.log("move circle up and to the right"); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - // calculate center and radius from circle itself - let cnx = stateVariables["/_circle1"].stateValues.numericalCenter[0]; - let cny = stateVariables["/_circle1"].stateValues.numericalCenter[1]; - let r = stateVariables["/_circle1"].stateValues.numericalRadius; - - let desireddx = 5, - desireddy = 3; - - let desiredcnx = cnx + desireddx; - let desiredcny = cny + desireddy; - - let dx = 6; - let dy = 4; - - cnx += dx; - cny += dy; - t1x += dx; - t1y += dy; - t2x += dx; - t2y += dy; - t3x += dx; - t3y += dy; - await win.callAction1({ - actionName: "moveCircle", - componentName: "/_circle1", - args: { center: [desiredcnx, desiredcny] }, - }); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, - ); - cy.get(cesc("#\\/centerPoint2")).should( + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, - ); - cy.get(cesc("#\\/radiusNumber")).should( - "contain.text", - nInDOM(Math.trunc(r * 100) / 100), - ); - - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - await stateVariables["/graph4/circle"].stateValues.radius, - ).closeTo(r, 1e-12); + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t1y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t2x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t2y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t3x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t3y * 100) / 100)}`, + ); + }); + + cy.log("move circle up and to the right"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + // calculate center and radius from circle itself + let cnx = stateVariables["/_circle1"].stateValues.numericalCenter[0]; + let cny = stateVariables["/_circle1"].stateValues.numericalCenter[1]; + + let desireddx = 5, + desireddy = 3; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = 6; + let dy = 4; + + t1x += desireddx; + t1y += desireddy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t1y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t2x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t2y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t3x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t3y * 100) / 100)}`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( t1x, 1e-12, @@ -21614,18 +25765,6 @@ describe("Circle Tag Tests", function () { t3y, 1e-12, ); - expect( - (await stateVariables["/centerPoint"].stateValues.xs)[0], - ).closeTo(cnx, 1e-12); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); }); }); @@ -21635,7 +25774,6 @@ describe("Circle Tag Tests", function () { // calculate center and radius from circle itself let cnx = stateVariables["/graph3/circle"].stateValues.numericalCenter[0]; let cny = stateVariables["/graph3/circle"].stateValues.numericalCenter[1]; - let r = stateVariables["/graph3/circle"].stateValues.numericalRadius; let desireddx = -4.9, desireddy = -2.2; @@ -21646,99 +25784,53 @@ describe("Circle Tag Tests", function () { let dx = -6; let dy = -2; - cnx += dx; - cny += dy; - t1x += dx; - t1y += dy; + t1x += desireddx; + t1y += desireddy; t2x += dx; t2y += dy; t3x += dx; t3y += dy; + t1x = Math.round(t1x * 1e14) / 1e14; + t1y = Math.round(t1y * 1e14) / 1e14; + t2x = Math.round(t2x * 1e14) / 1e14; + t2y = Math.round(t2y * 1e14) / 1e14; + t3x = Math.round(t3x * 1e14) / 1e14; + t3y = Math.round(t3y * 1e14) / 1e14; + await win.callAction1({ actionName: "moveCircle", componentName: "/graph3/circle", args: { center: [desiredcnx, desiredcny] }, }); - - cy.get(cesc("#\\/centerPoint2")).should( + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, ); - cy.get(cesc("#\\/centerPoint2")).should( + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `${nInDOM(Math.trunc(t1y * 100) / 100)}`, ); - cy.get(cesc("#\\/radiusNumber")).should( + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( "contain.text", - nInDOM(Math.trunc(r * 100) / 100), + `(${nInDOM(Math.trunc(t2x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t2y * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t3x * 100) / 100)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t3y * 100) / 100)}`, ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - await stateVariables["/graph4/circle"].stateValues.radius, - ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( t1x, 1e-12, @@ -21763,18 +25855,6 @@ describe("Circle Tag Tests", function () { t3y, 1e-12, ); - expect( - (await stateVariables["/centerPoint"].stateValues.xs)[0], - ).closeTo(cnx, 1e-12); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); }); }); @@ -21784,7 +25864,6 @@ describe("Circle Tag Tests", function () { // calculate center and radius from circle itself let cnx = stateVariables["/graph4/circle"].stateValues.numericalCenter[0]; let cny = stateVariables["/graph4/circle"].stateValues.numericalCenter[1]; - let r = stateVariables["/graph4/circle"].stateValues.numericalRadius; let desireddx = 7.1, desireddy = -2.9; @@ -21795,102 +25874,53 @@ describe("Circle Tag Tests", function () { let dx = 6; let dy = -2; - cnx += dx; - cny += dy; - t1x += dx; - t1y += dy; + t1x += desireddx; + t1y += desireddy; t2x += dx; t2y += dy; t3x += dx; t3y += dy; - cnx = Math.round(cnx * 1e14) / 1e14; - cny = Math.round(cny * 1e14) / 1e14; + t1x = Math.round(t1x * 1e14) / 1e14; + t1y = Math.round(t1y * 1e14) / 1e14; + t2x = Math.round(t2x * 1e14) / 1e14; + t2y = Math.round(t2y * 1e14) / 1e14; + t3x = Math.round(t3x * 1e14) / 1e14; + t3y = Math.round(t3y * 1e14) / 1e14; await win.callAction1({ actionName: "moveCircle", componentName: "/graph4/circle", args: { center: [desiredcnx, desiredcny] }, }); - - cy.get(cesc("#\\/centerPoint2")).should( + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( "contain.text", - `(${nInDOM(Math.trunc(cnx * 100) / 100)}`, + `(${nInDOM(Math.trunc(t1x * 1000) / 1000)}`, ); - cy.get(cesc("#\\/centerPoint2")).should( + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( "contain.text", - `${nInDOM(Math.trunc(cny * 100) / 100)}`, + `${nInDOM(Math.trunc(t1y * 1000) / 1000)}`, ); - cy.get(cesc("#\\/radiusNumber")).should( + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( "contain.text", - nInDOM(Math.trunc(r * 100) / 100), + `(${nInDOM(Math.trunc(t2x * 1000) / 1000)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t2y * 1000) / 1000)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t3x * 1000) / 1000)}`, + ); + cy.get(cesc("#\\/tp3") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t3y * 1000) / 1000)}`, ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect(stateVariables["/_circle1"].stateValues.numericalRadius).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/_circle1"].stateValues.radius).closeTo( - r, - 1e-12, - ); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - stateVariables["/graph3/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph3/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph3/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - await stateVariables["/graph3/circle"].stateValues.radius, - ).closeTo(r, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[0], - ).closeTo(cnx, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalCenter[1], - ).closeTo(cny, 1e-12); - expect( - stateVariables["/graph4/circle"].stateValues.numericalRadius, - ).closeTo(r, 1e-12); - expect(stateVariables["/graph4/circle"].stateValues.center[0]).closeTo( - cnx, - 1e-12, - ); - expect(stateVariables["/graph4/circle"].stateValues.center[1]).closeTo( - cny, - 1e-12, - ); - expect( - await stateVariables["/graph4/circle"].stateValues.radius, - ).closeTo(r, 1e-12); expect(stateVariables["/_point1"].stateValues.xs[0]).closeTo( t1x, 1e-12, @@ -21915,18 +25945,6 @@ describe("Circle Tag Tests", function () { t3y, 1e-12, ); - expect( - (await stateVariables["/centerPoint"].stateValues.xs)[0], - ).closeTo(cnx, 1e-12); - expect(stateVariables["/centerPoint"].stateValues.xs[1]).closeTo( - cny, - 1e-12, - ); - expect(stateVariables["/radiusNumber"].stateValues.value).closeTo( - r, - 1e-12, - ); - expect(stateVariables["/diam"].stateValues.value).closeTo(2 * r, 1e-12); }); }); }); diff --git a/cypress/e2e/DoenetML/tagSpecific/line.cy.js b/cypress/e2e/DoenetML/tagSpecific/line.cy.js index 30a663bf47..eda596c0ee 100644 --- a/cypress/e2e/DoenetML/tagSpecific/line.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/line.cy.js @@ -1116,7 +1116,7 @@ describe("Line Tag Tests", function () { ) (5,3) - + +1 @@ -4547,7 +4547,7 @@ describe("Line Tag Tests", function () { a (-5,9) - + @@ -5503,7 +5503,7 @@ describe("Line Tag Tests", function () { - + @@ -6220,7 +6220,7 @@ describe("Line Tag Tests", function () { doenetML: ` a - + @@ -6575,7 +6575,7 @@ describe("Line Tag Tests", function () { doenetML: ` a - + @@ -12042,4 +12042,194 @@ describe("Line Tag Tests", function () { "rgb(0, 0, 255)", ); }); + + it("line through two points, one constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + (-4,-1) + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc2("#/_text1")).should("have.text", "a"); // to wait for page to load + + let x1 = 4, + y1 = 6; + let x2 = -4, + y2 = -1; + + cy.get(cesc2("#/Pa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x1)},${nInDOM(y1)})`, + ); + cy.get(cesc2("#/Qa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x2)},${nInDOM(y2)})`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/P"].stateValues.xs[0]).eq(x1); + expect(stateVariables["/P"].stateValues.xs[1]).eq(y1); + expect(stateVariables["/P"].stateValues.coords).eqls(["vector", x1, y1]); + expect(stateVariables["/Q"].stateValues.xs[0]).eq(x2); + expect(stateVariables["/Q"].stateValues.xs[1]).eq(y2); + expect(stateVariables["/Q"].stateValues.coords).eqls(["vector", x2, y2]); + }); + + cy.log( + "move line down 4 and right 0.5 actually moves it down 3 and right none", + ); + cy.window().then(async (win) => { + let dx = 0.5, + dy = -4; + + let x1Desired = x1 + dx; + let y1Desired = y1 + dy; + let x2Desired = x2 + dx; + let y2Desired = y2 + dy; + + dx = 0; + dy = -3; + x1 += dx; + y1 += dy; + x2 += dx; + y2 += dy; + + win.callAction1({ + actionName: "moveLine", + componentName: "/_line1", + args: { + point1coords: [x1Desired, y1Desired], + point2coords: [x2Desired, y2Desired], + }, + }); + + cy.get(cesc2("#/Pa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x1)},${nInDOM(y1)})`, + ); + cy.get(cesc2("#/Qa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x2)},${nInDOM(y2)})`, + ); + }); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/P"].stateValues.xs[0]).eq(x1); + expect(stateVariables["/P"].stateValues.xs[1]).eq(y1); + expect(stateVariables["/P"].stateValues.coords).eqls(["vector", x1, y1]); + expect(stateVariables["/Q"].stateValues.xs[0]).eq(x2); + expect(stateVariables["/Q"].stateValues.xs[1]).eq(y2); + expect(stateVariables["/Q"].stateValues.coords).eqls(["vector", x2, y2]); + }); + }); + + it("line through two points, one constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + (-4,-1) + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc2("#/_text1")).should("have.text", "a"); // to wait for page to load + + let x1 = 4, + y1 = 6; + let x2 = -4, + y2 = -1; + + cy.get(cesc2("#/Pa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x1)},${nInDOM(y1)})`, + ); + cy.get(cesc2("#/Qa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x2)},${nInDOM(y2)})`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/P"].stateValues.xs[0]).eq(x1); + expect(stateVariables["/P"].stateValues.xs[1]).eq(y1); + expect(stateVariables["/P"].stateValues.coords).eqls(["vector", x1, y1]); + expect(stateVariables["/Q"].stateValues.xs[0]).eq(x2); + expect(stateVariables["/Q"].stateValues.xs[1]).eq(y2); + expect(stateVariables["/Q"].stateValues.coords).eqls(["vector", x2, y2]); + }); + + cy.log("move line down 4 and right 0.5"); + cy.window().then(async (win) => { + let dx = 0.5, + dy = -4; + + let x1Desired = x1 + dx; + let y1Desired = y1 + dy; + let x2Desired = x2 + dx; + let y2Desired = y2 + dy; + + dx = 0; + dy = -3; + x1 += dx; + y1 += dy; + x2 = x2Desired; + y2 = y2Desired; + + win.callAction1({ + actionName: "moveLine", + componentName: "/_line1", + args: { + point1coords: [x1Desired, y1Desired], + point2coords: [x2Desired, y2Desired], + }, + }); + + cy.get(cesc2("#/Pa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x1)},${nInDOM(y1)})`, + ); + cy.get(cesc2("#/Qa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x2)},${nInDOM(y2)})`, + ); + }); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/P"].stateValues.xs[0]).eq(x1); + expect(stateVariables["/P"].stateValues.xs[1]).eq(y1); + expect(stateVariables["/P"].stateValues.coords).eqls(["vector", x1, y1]); + expect(stateVariables["/Q"].stateValues.xs[0]).eq(x2); + expect(stateVariables["/Q"].stateValues.xs[1]).eq(y2); + expect(stateVariables["/Q"].stateValues.coords).eqls(["vector", x2, y2]); + }); + }); }); diff --git a/cypress/e2e/DoenetML/tagSpecific/linesegment.cy.js b/cypress/e2e/DoenetML/tagSpecific/linesegment.cy.js index 5f25286975..5ce132d004 100644 --- a/cypress/e2e/DoenetML/tagSpecific/linesegment.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/linesegment.cy.js @@ -4650,4 +4650,196 @@ describe("LineSegment Tag Tests", function () { "C is a thin white line segment.", ); }); + + it("line segment based on two endpoints, one constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + (-4,-1) + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc2("#/_text1")).should("have.text", "a"); // to wait for page to load + + let x1 = 4, + y1 = 6; + let x2 = -4, + y2 = -1; + + cy.get(cesc2("#/Pa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x1)},${nInDOM(y1)})`, + ); + cy.get(cesc2("#/Qa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x2)},${nInDOM(y2)})`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/P"].stateValues.xs[0]).eq(x1); + expect(stateVariables["/P"].stateValues.xs[1]).eq(y1); + expect(stateVariables["/P"].stateValues.coords).eqls(["vector", x1, y1]); + expect(stateVariables["/Q"].stateValues.xs[0]).eq(x2); + expect(stateVariables["/Q"].stateValues.xs[1]).eq(y2); + expect(stateVariables["/Q"].stateValues.coords).eqls(["vector", x2, y2]); + }); + + cy.log( + "move line down 4 and right 0.5 actually moves it down 3 and right none", + ); + cy.window().then(async (win) => { + let dx = 0.5, + dy = -4; + + let x1Desired = x1 + dx; + let y1Desired = y1 + dy; + let x2Desired = x2 + dx; + let y2Desired = y2 + dy; + + dx = 0; + dy = -3; + x1 += dx; + y1 += dy; + x2 += dx; + y2 += dy; + + win.callAction1({ + actionName: "moveLineSegment", + componentName: "/_linesegment1", + args: { + point1coords: [x1Desired, y1Desired], + point2coords: [x2Desired, y2Desired], + }, + }); + + cy.get(cesc2("#/Pa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x1)},${nInDOM(y1)})`, + ); + cy.get(cesc2("#/Qa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x2)},${nInDOM(y2)})`, + ); + }); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/P"].stateValues.xs[0]).eq(x1); + expect(stateVariables["/P"].stateValues.xs[1]).eq(y1); + expect(stateVariables["/P"].stateValues.coords).eqls(["vector", x1, y1]); + expect(stateVariables["/Q"].stateValues.xs[0]).eq(x2); + expect(stateVariables["/Q"].stateValues.xs[1]).eq(y2); + expect(stateVariables["/Q"].stateValues.coords).eqls(["vector", x2, y2]); + }); + }); + + it("line segment based on two endpoints, one constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + (-4,-1) + + + + + `, + }, + "*", + ); + }); + + cy.get(cesc2("#/_text1")).should("have.text", "a"); // to wait for page to load + + let x1 = 4, + y1 = 6; + let x2 = -4, + y2 = -1; + + cy.get(cesc2("#/Pa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x1)},${nInDOM(y1)})`, + ); + cy.get(cesc2("#/Qa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x2)},${nInDOM(y2)})`, + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/P"].stateValues.xs[0]).eq(x1); + expect(stateVariables["/P"].stateValues.xs[1]).eq(y1); + expect(stateVariables["/P"].stateValues.coords).eqls(["vector", x1, y1]); + expect(stateVariables["/Q"].stateValues.xs[0]).eq(x2); + expect(stateVariables["/Q"].stateValues.xs[1]).eq(y2); + expect(stateVariables["/Q"].stateValues.coords).eqls(["vector", x2, y2]); + }); + + cy.log( + "move line down 4 and right 0.5 actually moves it down 3 and right none", + ); + cy.window().then(async (win) => { + let dx = 0.5, + dy = -4; + + let x1Desired = x1 + dx; + let y1Desired = y1 + dy; + let x2Desired = x2 + dx; + let y2Desired = y2 + dy; + + dx = 0; + dy = -3; + x1 += dx; + y1 += dy; + x2 = x2Desired; + y2 = y2Desired; + + win.callAction1({ + actionName: "moveLineSegment", + componentName: "/_linesegment1", + args: { + point1coords: [x1Desired, y1Desired], + point2coords: [x2Desired, y2Desired], + }, + }); + + cy.get(cesc2("#/Pa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x1)},${nInDOM(y1)})`, + ); + cy.get(cesc2("#/Qa") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(x2)},${nInDOM(y2)})`, + ); + }); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/P"].stateValues.xs[0]).eq(x1); + expect(stateVariables["/P"].stateValues.xs[1]).eq(y1); + expect(stateVariables["/P"].stateValues.coords).eqls(["vector", x1, y1]); + expect(stateVariables["/Q"].stateValues.xs[0]).eq(x2); + expect(stateVariables["/Q"].stateValues.xs[1]).eq(y2); + expect(stateVariables["/Q"].stateValues.coords).eqls(["vector", x2, y2]); + }); + }); }); diff --git a/cypress/e2e/DoenetML/tagSpecific/polygon.cy.js b/cypress/e2e/DoenetML/tagSpecific/polygon.cy.js index f60cb6657a..c4f290ca2c 100644 --- a/cypress/e2e/DoenetML/tagSpecific/polygon.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/polygon.cy.js @@ -4419,4 +4419,708 @@ describe("Polygon Tag Tests", function () { ); cy.get(cesc("#\\/Cfilldescrip")).should("have.text", "C has a white fill."); }); + + it("One vertex constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + (-4,-1) + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 5], + [-4, -1], + [6, 4], + [-3, 4], + ]; + + testPolygonCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("move copied polygon up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polygon down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolygonCopiedTwice({ vertices }); + }); + }); + + it("One vertex constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + (-4,-1) + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 5], + [-4, -1], + [6, 4], + [-3, 4], + ]; + + testPolygonCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("move copied polygon up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + vertices[2][0] = vertices[2][0] + moveX; + vertices[2][1] = vertices[2][1] + moveY; + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polygon down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + vertices[2][0] = vertices[2][0] + moveX; + vertices[2][1] = vertices[2][1] + moveY; + + testPolygonCopiedTwice({ vertices }); + }); + }); + + it("Two vertices constrained to same grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + + + (-4,-1) + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 4], + [-4, -1], + [6, 4], + [-3, 4], + ]; + + testPolygonCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("move copied polygon up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polygon down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolygonCopiedTwice({ vertices }); + }); + }); + + it("Two vertices constrained to same grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + + + (-4,-1) + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 4], + [-4, -1], + [6, 4], + [-3, 4], + ]; + + testPolygonCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("move copied polygon up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + for (let i = 0; i < vertices.length; i++) { + if ([0, 2].includes(i)) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + } + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polygon down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + for (let i = 0; i < vertices.length; i++) { + if ([0, 2].includes(i)) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + } + + testPolygonCopiedTwice({ vertices }); + }); + }); + + it("Three vertices constrained to same grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + + + (-4,-1) + + + + + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 4], + [-3, 0], + [6, 4], + [-3, 4], + ]; + + testPolygonCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + // adjust for constraint + vertices[1] = [3, 8]; + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("move copied polygon up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polygon down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolygonCopiedTwice({ vertices }); + }); + }); + + it("Three vertices constrained to same grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + + + (-4,-1) + + + + + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 4], + [-3, 0], + [6, 4], + [-3, 4], + ]; + + testPolygonCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + // adjust for constraint + vertices[1] = [3, 8]; + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("move copied polygon up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + // all but last vertex + for (let i = 0; i < vertices.length - 1; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolygonCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polygon down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolygon", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + // all but last vertex + for (let i = 0; i < vertices.length - 1; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolygonCopiedTwice({ vertices }); + }); + }); }); diff --git a/cypress/e2e/DoenetML/tagSpecific/polyline.cy.js b/cypress/e2e/DoenetML/tagSpecific/polyline.cy.js index 141d934228..c38d8b7631 100644 --- a/cypress/e2e/DoenetML/tagSpecific/polyline.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/polyline.cy.js @@ -4256,4 +4256,708 @@ describe("Polyline Tag Tests", function () { "C is a thin white polyline.", ); }); + + it("One vertex constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + (-4,-1) + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 5], + [-4, -1], + [6, 4], + [-3, 4], + ]; + + testPolylineCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("move copied polyline up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polyline down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolylineCopiedTwice({ vertices }); + }); + }); + + it("One vertex constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + (-4,-1) + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 5], + [-4, -1], + [6, 4], + [-3, 4], + ]; + + testPolylineCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("move copied polyline up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + vertices[2][0] = vertices[2][0] + moveX; + vertices[2][1] = vertices[2][1] + moveY; + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polyline down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + vertices[2][0] = vertices[2][0] + moveX; + vertices[2][1] = vertices[2][1] + moveY; + + testPolylineCopiedTwice({ vertices }); + }); + }); + + it("Two vertices constrained to same grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + + + (-4,-1) + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 4], + [-4, -1], + [6, 4], + [-3, 4], + ]; + + testPolylineCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("move copied polyline up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polyline down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolylineCopiedTwice({ vertices }); + }); + }); + + it("Two vertices constrained to same grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + + + (-4,-1) + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 4], + [-4, -1], + [6, 4], + [-3, 4], + ]; + + testPolylineCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("move copied polyline up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + for (let i = 0; i < vertices.length; i++) { + if ([0, 2].includes(i)) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + } + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polyline down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + for (let i = 0; i < vertices.length; i++) { + if ([0, 2].includes(i)) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + } + + testPolylineCopiedTwice({ vertices }); + }); + }); + + it("Three vertices constrained to same grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + + + (-4,-1) + + + + + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 4], + [-3, 0], + [6, 4], + [-3, 4], + ]; + + testPolylineCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + // adjust for constraint + vertices[1] = [3, 8]; + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("move copied polyline up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polyline down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolylineCopiedTwice({ vertices }); + }); + }); + + it("Three vertices constrained to same grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (3,5) + + + + + (-4,-1) + + + + + (5,2) + + + + + (-3,4) + + + + + + + + `, + }, + "*", + ); + }); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); //wait for page to load + + let vertices = [ + [3, 4], + [-3, 0], + [6, 4], + [-3, 4], + ]; + + testPolylineCopiedTwice({ vertices }); + + cy.log("move individual vertex"); + cy.window().then(async (win) => { + vertices[1] = [4, 7]; + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g1/pg", + args: { + pointCoords: { 1: vertices[1] }, + }, + }); + + // adjust for constraint + vertices[1] = [3, 8]; + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("move copied polyline up and to the right"); + cy.window().then(async (win) => { + let moveX = 4; + let moveY = 3; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g2/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = 1; + // all but last vertex + for (let i = 0; i < vertices.length - 1; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolylineCopiedTwice({ vertices }); + }); + + cy.log("try to move double copied polyline down and to the right"); + cy.window().then(async (win) => { + let moveX = 1; + let moveY = -7; + + for (let i = 0; i < vertices.length; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + win.callAction1({ + actionName: "movePolyline", + componentName: "/g3/pg", + args: { + pointCoords: vertices, + }, + }); + + // adjustment due to constraint + moveX = -1; + moveY = -1; + // all but last vertex + for (let i = 0; i < vertices.length - 1; i++) { + vertices[i][0] = vertices[i][0] + moveX; + vertices[i][1] = vertices[i][1] + moveY; + } + + testPolylineCopiedTwice({ vertices }); + }); + }); }); diff --git a/cypress/e2e/DoenetML/tagSpecific/ray.cy.js b/cypress/e2e/DoenetML/tagSpecific/ray.cy.js index ea64fadc4b..3da559f10b 100644 --- a/cypress/e2e/DoenetML/tagSpecific/ray.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/ray.cy.js @@ -10362,4 +10362,686 @@ describe("Ray Tag Tests", function () { cy.get(cesc("#\\/Bdescrip")).should("have.text", "B is a light red ray."); cy.get(cesc("#\\/Cdescrip")).should("have.text", "C is a thin white ray."); }); + + it("ray with through and endpoint, endpoint constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (4,1) + + + + + (-4,2) + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + // to wait for page to load + cy.get(cesc("#\\/_text1")).should("have.text", "a"); + + let endpointx = 5; + let endpointy = 0; + let throughx = -4; + let throughy = 2; + let directionEndpointShiftx = 0; + let directionEndpointShifty = 0; + + cy.window().then(async (win) => { + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move ray up and to the right"); + cy.window().then(async (win) => { + let moveX = 3; + let moveY = 2; + endpointx += moveX; + throughx += moveX; + endpointy += moveY; + throughy += moveY; + + win.callAction1({ + actionName: "moveRay", + componentName: "/_ray1", + args: { + endpointcoords: [endpointx, endpointy], + throughcoords: [throughx, throughy], + }, + }); + + // adjust for constraints + moveX = 2; + moveY = 1; + endpointx += moveX; + throughx += moveX; + endpointy += moveY; + throughy += moveY; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied through"); + cy.window().then(async (win) => { + throughx = -5; + throughy = 7; + + win.callAction1({ + actionName: "movePoint", + componentName: "/through", + args: { x: throughx, y: throughy }, + }); + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied endpoint"); + cy.window().then(async (win) => { + endpointx = -3; + endpointy = -9; + + win.callAction1({ + actionName: "movePoint", + componentName: "/endpoint", + args: { x: endpointx, y: endpointy }, + }); + + // adjust for constraints + endpointx = -5; + endpointy = -9; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied direction"); + cy.window().then(async (win) => { + let directionEndpointShiftx = -4; + let directionEndpointShifty = -5; + + let directionx = 2; + let directiony = -3; + + throughx = endpointx + directionx; + throughy = endpointy + directiony; + + let directionthroughx = directionEndpointShiftx + directionx; + let directionthroughy = directionEndpointShifty + directiony; + + win.callAction1({ + actionName: "moveVector", + componentName: "/direction", + args: { + tailcoords: [directionEndpointShiftx, directionEndpointShifty], + headcoords: [directionthroughx, directionthroughy], + }, + }); + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + }); + + it("ray with through and endpoint, endpoint constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (4,1) + + + + + (-4,2) + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + // to wait for page to load + cy.get(cesc("#\\/_text1")).should("have.text", "a"); + + let endpointx = 5; + let endpointy = 0; + let throughx = -4; + let throughy = 2; + let directionEndpointShiftx = 0; + let directionEndpointShifty = 0; + + cy.window().then(async (win) => { + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move ray up and to the right"); + cy.window().then(async (win) => { + let moveX = 3; + let moveY = 2; + endpointx += moveX; + throughx += moveX; + endpointy += moveY; + throughy += moveY; + + win.callAction1({ + actionName: "moveRay", + componentName: "/_ray1", + args: { + endpointcoords: [endpointx, endpointy], + throughcoords: [throughx, throughy], + }, + }); + + // adjust for constraints + moveX = 2; + moveY = 1; + endpointx += moveX; + endpointy += moveY; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied through"); + cy.window().then(async (win) => { + throughx = -5; + throughy = 7; + + win.callAction1({ + actionName: "movePoint", + componentName: "/through", + args: { x: throughx, y: throughy }, + }); + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied endpoint"); + cy.window().then(async (win) => { + endpointx = -3; + endpointy = -9; + + win.callAction1({ + actionName: "movePoint", + componentName: "/endpoint", + args: { x: endpointx, y: endpointy }, + }); + + // adjust for constraints + endpointx = -5; + endpointy = -9; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied direction"); + cy.window().then(async (win) => { + let directionEndpointShiftx = -4; + let directionEndpointShifty = -5; + + let directionx = 2; + let directiony = -3; + + throughx = endpointx + directionx; + throughy = endpointy + directiony; + + let directionthroughx = directionEndpointShiftx + directionx; + let directionthroughy = directionEndpointShifty + directiony; + + win.callAction1({ + actionName: "moveVector", + componentName: "/direction", + args: { + tailcoords: [directionEndpointShiftx, directionEndpointShifty], + headcoords: [directionthroughx, directionthroughy], + }, + }); + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + }); + + it("ray with through and endpoint, through point constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (4,1) + (-4,2) + + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + // to wait for page to load + cy.get(cesc("#\\/_text1")).should("have.text", "a"); + + let endpointx = 4; + let endpointy = 1; + let throughx = -5; + let throughy = 3; + let directionEndpointShiftx = 0; + let directionEndpointShifty = 0; + + cy.window().then(async (win) => { + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move ray up and to the right"); + cy.window().then(async (win) => { + let moveX = 3; + let moveY = 2; + endpointx += moveX; + throughx += moveX; + endpointy += moveY; + throughy += moveY; + + win.callAction1({ + actionName: "moveRay", + componentName: "/_ray1", + args: { + endpointcoords: [endpointx, endpointy], + throughcoords: [throughx, throughy], + }, + }); + + // adjust for constraints + moveX = 2; + moveY = 1; + endpointx += moveX; + throughx += moveX; + endpointy += moveY; + throughy += moveY; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied through"); + cy.window().then(async (win) => { + throughx = -5; + throughy = 7; + + win.callAction1({ + actionName: "movePoint", + componentName: "/through", + args: { x: throughx, y: throughy }, + }); + + // adjust for constraints + throughx = -5; + throughy = 6; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied endpoint"); + cy.window().then(async (win) => { + endpointx = -3; + endpointy = -9; + + win.callAction1({ + actionName: "movePoint", + componentName: "/endpoint", + args: { x: endpointx, y: endpointy }, + }); + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied direction"); + cy.window().then(async (win) => { + let directionEndpointShiftx = -4; + let directionEndpointShifty = -5; + + let directionx = 2; + let directiony = -3; + + throughx = endpointx + directionx; + throughy = endpointy + directiony; + + let directionthroughx = directionEndpointShiftx + directionx; + let directionthroughy = directionEndpointShifty + directiony; + + win.callAction1({ + actionName: "moveVector", + componentName: "/direction", + args: { + tailcoords: [directionEndpointShiftx, directionEndpointShifty], + headcoords: [directionthroughx, directionthroughy], + }, + }); + + // adjust for constraints + throughx = Math.round(throughx / 5) * 5; + throughy = Math.round(throughy / 3) * 3; + throughx = throughx === 0 ? 0 : throughx; // change -0 to 0 + directionx = throughx - endpointx; + directiony = throughy - endpointy; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + }); + + it("ray with through and endpoint, through point constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (4,1) + (-4,2) + + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + // to wait for page to load + cy.get(cesc("#\\/_text1")).should("have.text", "a"); + + let endpointx = 4; + let endpointy = 1; + let throughx = -5; + let throughy = 3; + let directionEndpointShiftx = 0; + let directionEndpointShifty = 0; + + cy.window().then(async (win) => { + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move ray up and to the right"); + cy.window().then(async (win) => { + let moveX = 3; + let moveY = 2; + endpointx += moveX; + throughx += moveX; + endpointy += moveY; + throughy += moveY; + + win.callAction1({ + actionName: "moveRay", + componentName: "/_ray1", + args: { + endpointcoords: [endpointx, endpointy], + throughcoords: [throughx, throughy], + }, + }); + + // adjust for constraints + moveX = 2; + moveY = 1; + throughx += moveX; + throughy += moveY; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied through"); + cy.window().then(async (win) => { + throughx = -5; + throughy = 7; + + win.callAction1({ + actionName: "movePoint", + componentName: "/through", + args: { x: throughx, y: throughy }, + }); + + // adjust for constraints + throughx = -5; + throughy = 6; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied endpoint"); + cy.window().then(async (win) => { + endpointx = -3; + endpointy = -9; + + win.callAction1({ + actionName: "movePoint", + componentName: "/endpoint", + args: { x: endpointx, y: endpointy }, + }); + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + + cy.log("move copied direction"); + cy.window().then(async (win) => { + let directionEndpointShiftx = -4; + let directionEndpointShifty = -5; + + let directionx = 2; + let directiony = -3; + + throughx = endpointx + directionx; + throughy = endpointy + directiony; + + let directionthroughx = directionEndpointShiftx + directionx; + let directionthroughy = directionEndpointShifty + directiony; + + win.callAction1({ + actionName: "moveVector", + componentName: "/direction", + args: { + tailcoords: [directionEndpointShiftx, directionEndpointShifty], + headcoords: [directionthroughx, directionthroughy], + }, + }); + + // adjust for constraints + throughx = Math.round(throughx / 5) * 5; + throughy = Math.round(throughy / 3) * 3; + throughx = throughx === 0 ? 0 : throughx; // change -0 to 0 + directionx = throughx - endpointx; + directiony = throughy - endpointy; + + await testRayCopiedHTD({ + throughx, + throughy, + endpointx, + endpointy, + directionEndpointShiftx, + directionEndpointShifty, + }); + }); + }); }); diff --git a/cypress/e2e/DoenetML/tagSpecific/rectangle.cy.js b/cypress/e2e/DoenetML/tagSpecific/rectangle.cy.js index 247e5b3e64..21c9f5cb82 100644 --- a/cypress/e2e/DoenetML/tagSpecific/rectangle.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/rectangle.cy.js @@ -1,5 +1,5 @@ import me from "math-expressions"; -import { cesc } from "../../../../src/_utils/url"; +import { cesc, cesc2 } from "../../../../src/_utils/url"; function nInDOM(n) { if (n < 0) { @@ -1215,6 +1215,383 @@ describe("Rectangle Tag Tests", function () { expect(stateVariables["/p"].stateValues.verticesDraggable).eq(false); }); }); + + it("single vertex constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + + + + + + +

corner vertices: $p.vertex1{assignNames="v1"} $p.vertex3{assignNames="v3"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,4)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(1,5)"); + + cy.log("move rectangle"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords: [ + [8, 9], + [9, 9], + [9, 10], + [8, 10], + ], + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(9,10)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(9,10)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(10,11)"); + }); + + it("two vertices, first vertex constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + + + + + (6,5) + + +

corner vertices: $p.vertex1{assignNames="v1"} $p.vertex3{assignNames="v3"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,4)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(6,5)"); + + cy.log("move rectangle"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords: [ + [-8, -9], + [-2, -9], + [-2, -8], + [-8, -8], + ], + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−9,−8)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−9,−8)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−3,−7)"); + }); + + it("two vertices, first vertex constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + + + + + (6,5) + + +

corner vertices: $p.vertex1{assignNames="v1"} $p.vertex3{assignNames="v3"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,4)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(6,5)"); + + cy.log("move rectangle"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords: [ + [-8, -9], + [-2, -9], + [-2, -8], + [-8, -8], + ], + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−9,−8)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−9,−8)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−2,−8)"); + }); + + it("center and vertex, vertex constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + + + + + (6,5) + + +

corner vertices: $p.vertex1{assignNames="v1"} $p.vertex3{assignNames="v3"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,4)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(12,6)"); + + cy.log("move rectangle"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords: [ + [-8, -9], + [4, -9], + [4, -7], + [-8, -7], + ], + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−9,−8)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−9,−8)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(3,−6)"); + }); + + it("center and vertex, vertex constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + + + + + (6,5) + + +

corner vertices: $p.vertex1{assignNames="v1"} $p.vertex3{assignNames="v3"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,4)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(12,6)"); + + cy.log("move rectangle"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords: [ + [-8, -9], + [4, -9], + [4, -7], + [-8, -7], + ], + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−9,−8)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−9,−8)"); + + // keep center at (-2, -8) + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(5,−8)"); + }); + + it("center and vertex, center constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + (6,5) + + + + + + +

corner vertices: $p.vertex1{assignNames="v1"} $p.vertex3{assignNames="v3"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(1,3)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(11,9)"); + + cy.log("move rectangle"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords: [ + [-9, -8], + [1, -8], + [1, -2], + [-9, -2], + ], + }, + }); + }); + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−8,−7)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−8,−7)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(2,−1)"); + }); + + it("center and vertex, center constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + (6,5) + + + + + + +

corner vertices: $p.vertex1{assignNames="v1"} $p.vertex3{assignNames="v3"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(1,3)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(11,9)"); + + cy.log("move rectangle"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords: [ + [-9, -8], + [1, -8], + [1, -2], + [-9, -2], + ], + }, + }); + }); + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−9,−8)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−9,−8)"); + cy.get(cesc2("#/v3") + " .mjx-mrow") + .eq(0) + .should("have.text", "(3,0)"); + }); }); function setupScene({ rectangleProperties, rectangleChildren }) { diff --git a/cypress/e2e/DoenetML/tagSpecific/regularPolygon2.cy.js b/cypress/e2e/DoenetML/tagSpecific/regularPolygon2.cy.js index 7798c18776..e31060c2cd 100644 --- a/cypress/e2e/DoenetML/tagSpecific/regularPolygon2.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/regularPolygon2.cy.js @@ -1,4 +1,4 @@ -import { cesc } from "../../../../src/_utils/url"; +import { cesc, cesc2 } from "../../../../src/_utils/url"; import { runTests, setupScene } from "./regularPolygonUtils"; describe("Regular Polygon Tag Tests", function () { @@ -678,4 +678,368 @@ describe("Regular Polygon Tag Tests", function () { expect(stateVariables["/p"].stateValues.verticesDraggable).eq(false); }); }); + + it("two vertices, first vertex constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + + + + + (6,5) + + +

First two vertices: $p.vertex1{assignNames="v1"} $p.vertex2{assignNames="v2" displaySmallAsZero}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,4)"); + cy.get(cesc2("#/v2") + " .mjx-mrow") + .eq(0) + .should("have.text", "(6,5)"); + + cy.log("move pentagon"); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let numericalVertices = + stateVariables["/p"].stateValues.numericalVertices; + + let dx = -7; + let dy = -5; + + let pointCoords = numericalVertices.map((v) => [v[0] + dx, v[1] + dy]); + + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords, + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−6,0)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−6,0)"); + cy.get(cesc2("#/v2") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,1)"); + }); + + it("two vertices, first vertex constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + + + + + (6,5) + + +

First two vertices: $p.vertex1{assignNames="v1"} $p.vertex2{assignNames="v2" displaySmallAsZero}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,4)"); + cy.get(cesc2("#/v2") + " .mjx-mrow") + .eq(0) + .should("have.text", "(6,5)"); + + cy.log("move pentagon"); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let numericalVertices = + stateVariables["/p"].stateValues.numericalVertices; + + let dx = -7; + let dy = -5; + + let pointCoords = numericalVertices.map((v) => [v[0] + dx, v[1] + dy]); + + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords, + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−6,0)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−6,0)"); + cy.get(cesc2("#/v2") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−1,0)"); + }); + + it("center and vertex, vertex constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + + + + + (6,5) + + +

First two vertex: $p.vertex1{assignNames="v1"}

+

Center: $p.center{assignNames="c"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,4)"); + cy.get(cesc2("#/c") + " .mjx-mrow") + .eq(0) + .should("have.text", "(6,5)"); + + cy.log("move pentagon"); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let numericalVertices = + stateVariables["/p"].stateValues.numericalVertices; + + let dx = -7; + let dy = -5; + + let pointCoords = numericalVertices.map((v) => [v[0] + dx, v[1] + dy]); + + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords, + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−6,0)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−6,0)"); + cy.get(cesc2("#/c") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,1)"); + }); + + it("center and vertex, vertex constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + + + + + (6,5) + + +

First vertex: $p.vertex1{assignNames="v1"}

+

Center: $p.center{assignNames="c" displaySmallAsZero}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,4)"); + cy.get(cesc2("#/c") + " .mjx-mrow") + .eq(0) + .should("have.text", "(6,5)"); + + cy.log("move pentagon"); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let numericalVertices = + stateVariables["/p"].stateValues.numericalVertices; + + let dx = -7; + let dy = -5; + + let pointCoords = numericalVertices.map((v) => [v[0] + dx, v[1] + dy]); + + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords, + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−6,0)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−6,0)"); + cy.get(cesc2("#/c") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−1,0)"); + }); + + it("center and vertex, center constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + (6,5) + + + + + + +

First vertex: $p.vertex1{assignNames="v1"}

+

Center: $p.center{assignNames="c"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(1,3)"); + cy.get(cesc2("#/c") + " .mjx-mrow") + .eq(0) + .should("have.text", "(6,6)"); + + cy.log("move pentagon"); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let numericalVertices = + stateVariables["/p"].stateValues.numericalVertices; + + let dx = -7; + let dy = -5; + + let pointCoords = numericalVertices.map((v) => [v[0] + dx, v[1] + dy]); + + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords, + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−5,−1)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−5,−1)"); + cy.get(cesc2("#/c") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,2)"); + }); + + it("center and vertex, center constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + + (1,3) + (6,5) + + + + + + +

First vertex: $p.vertex1{assignNames="v1"}

+

Center: $p.center{assignNames="c"}

+ `, + }, + "*", + ); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(1,3)"); + cy.get(cesc2("#/c") + " .mjx-mrow") + .eq(0) + .should("have.text", "(6,6)"); + + cy.log("move pentagon"); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + + let numericalVertices = + stateVariables["/p"].stateValues.numericalVertices; + + let dx = -7; + let dy = -5; + + let pointCoords = numericalVertices.map((v) => [v[0] + dx, v[1] + dy]); + + await win.callAction1({ + actionName: "movePolygon", + componentName: "/p", + args: { + pointCoords, + }, + }); + }); + + cy.get(cesc2("#/v1") + " .mjx-mrow").should("contain.text", "(−6,−2)"); + + cy.get(cesc2("#/v1") + " .mjx-mrow") + .eq(0) + .should("have.text", "(−6,−2)"); + cy.get(cesc2("#/c") + " .mjx-mrow") + .eq(0) + .should("have.text", "(0,2)"); + }); }); diff --git a/cypress/e2e/DoenetML/tagSpecific/vector.cy.js b/cypress/e2e/DoenetML/tagSpecific/vector.cy.js index 2a6d9965c4..99ae6bbc6b 100644 --- a/cypress/e2e/DoenetML/tagSpecific/vector.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/vector.cy.js @@ -19694,4 +19694,686 @@ describe("Vector Tag Tests", function () { "rgb(0, 0, 255)", ); }); + + it("vector with head and tail, tail constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (4,1) + + + + + (-4,2) + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + // to wait for page to load + cy.get(cesc("#\\/_text1")).should("have.text", "a"); + + let tailx = 5; + let taily = 0; + let headx = -4; + let heady = 2; + let displacementTailShiftx = 0; + let displacementTailShifty = 0; + + cy.window().then(async (win) => { + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move vector up and to the right"); + cy.window().then(async (win) => { + let moveX = 3; + let moveY = 2; + tailx += moveX; + headx += moveX; + taily += moveY; + heady += moveY; + + win.callAction1({ + actionName: "moveVector", + componentName: "/_vector1", + args: { + tailcoords: [tailx, taily], + headcoords: [headx, heady], + }, + }); + + // adjust for constraints + moveX = 2; + moveY = 1; + tailx += moveX; + headx += moveX; + taily += moveY; + heady += moveY; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied head"); + cy.window().then(async (win) => { + headx = -5; + heady = 7; + + win.callAction1({ + actionName: "movePoint", + componentName: "/head", + args: { x: headx, y: heady }, + }); + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied tail"); + cy.window().then(async (win) => { + tailx = -3; + taily = -9; + + win.callAction1({ + actionName: "movePoint", + componentName: "/tail", + args: { x: tailx, y: taily }, + }); + + // adjust for constraints + tailx = -5; + taily = -9; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied displacement"); + cy.window().then(async (win) => { + let displacementTailShiftx = -4; + let displacementTailShifty = -5; + + let displacementx = 2; + let displacementy = -3; + + headx = tailx + displacementx; + heady = taily + displacementy; + + let displacementheadx = displacementTailShiftx + displacementx; + let displacementheady = displacementTailShifty + displacementy; + + win.callAction1({ + actionName: "moveVector", + componentName: "/displacement", + args: { + tailcoords: [displacementTailShiftx, displacementTailShifty], + headcoords: [displacementheadx, displacementheady], + }, + }); + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + }); + + it("vector with head and tail, tail constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (4,1) + + + + + (-4,2) + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + // to wait for page to load + cy.get(cesc("#\\/_text1")).should("have.text", "a"); + + let tailx = 5; + let taily = 0; + let headx = -4; + let heady = 2; + let displacementTailShiftx = 0; + let displacementTailShifty = 0; + + cy.window().then(async (win) => { + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move vector up and to the right"); + cy.window().then(async (win) => { + let moveX = 3; + let moveY = 2; + tailx += moveX; + headx += moveX; + taily += moveY; + heady += moveY; + + win.callAction1({ + actionName: "moveVector", + componentName: "/_vector1", + args: { + tailcoords: [tailx, taily], + headcoords: [headx, heady], + }, + }); + + // adjust for constraints + moveX = 2; + moveY = 1; + tailx += moveX; + taily += moveY; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied head"); + cy.window().then(async (win) => { + headx = -5; + heady = 7; + + win.callAction1({ + actionName: "movePoint", + componentName: "/head", + args: { x: headx, y: heady }, + }); + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied tail"); + cy.window().then(async (win) => { + tailx = -3; + taily = -9; + + win.callAction1({ + actionName: "movePoint", + componentName: "/tail", + args: { x: tailx, y: taily }, + }); + + // adjust for constraints + tailx = -5; + taily = -9; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied displacement"); + cy.window().then(async (win) => { + let displacementTailShiftx = -4; + let displacementTailShifty = -5; + + let displacementx = 2; + let displacementy = -3; + + headx = tailx + displacementx; + heady = taily + displacementy; + + let displacementheadx = displacementTailShiftx + displacementx; + let displacementheady = displacementTailShifty + displacementy; + + win.callAction1({ + actionName: "moveVector", + componentName: "/displacement", + args: { + tailcoords: [displacementTailShiftx, displacementTailShifty], + headcoords: [displacementheadx, displacementheady], + }, + }); + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + }); + + it("vector with head and tail, head constrained to grid", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (4,1) + (-4,2) + + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + // to wait for page to load + cy.get(cesc("#\\/_text1")).should("have.text", "a"); + + let tailx = 4; + let taily = 1; + let headx = -5; + let heady = 3; + let displacementTailShiftx = 0; + let displacementTailShifty = 0; + + cy.window().then(async (win) => { + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move vector up and to the right"); + cy.window().then(async (win) => { + let moveX = 3; + let moveY = 2; + tailx += moveX; + headx += moveX; + taily += moveY; + heady += moveY; + + win.callAction1({ + actionName: "moveVector", + componentName: "/_vector1", + args: { + tailcoords: [tailx, taily], + headcoords: [headx, heady], + }, + }); + + // adjust for constraints + moveX = 2; + moveY = 1; + tailx += moveX; + headx += moveX; + taily += moveY; + heady += moveY; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied head"); + cy.window().then(async (win) => { + headx = -5; + heady = 7; + + win.callAction1({ + actionName: "movePoint", + componentName: "/head", + args: { x: headx, y: heady }, + }); + + // adjust for constraints + headx = -5; + heady = 6; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied tail"); + cy.window().then(async (win) => { + tailx = -3; + taily = -9; + + win.callAction1({ + actionName: "movePoint", + componentName: "/tail", + args: { x: tailx, y: taily }, + }); + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied displacement"); + cy.window().then(async (win) => { + let displacementTailShiftx = -4; + let displacementTailShifty = -5; + + let displacementx = 2; + let displacementy = -3; + + headx = tailx + displacementx; + heady = taily + displacementy; + + let displacementheadx = displacementTailShiftx + displacementx; + let displacementheady = displacementTailShifty + displacementy; + + win.callAction1({ + actionName: "moveVector", + componentName: "/displacement", + args: { + tailcoords: [displacementTailShiftx, displacementTailShifty], + headcoords: [displacementheadx, displacementheady], + }, + }); + + // adjust for constraints + headx = Math.round(headx / 5) * 5; + heady = Math.round(heady / 3) * 3; + headx = headx === 0 ? 0 : headx; // change -0 to 0 + displacementx = headx - tailx; + displacementy = heady - taily; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + }); + + it("vector with head and tail, head constrained to grid, allow flexible motion", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + (4,1) + (-4,2) + + + + + + + + + + + + + + + + + `, + }, + "*", + ); + }); + + // to wait for page to load + cy.get(cesc("#\\/_text1")).should("have.text", "a"); + + let tailx = 4; + let taily = 1; + let headx = -5; + let heady = 3; + let displacementTailShiftx = 0; + let displacementTailShifty = 0; + + cy.window().then(async (win) => { + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move vector up and to the right"); + cy.window().then(async (win) => { + let moveX = 3; + let moveY = 2; + tailx += moveX; + headx += moveX; + taily += moveY; + heady += moveY; + + win.callAction1({ + actionName: "moveVector", + componentName: "/_vector1", + args: { + tailcoords: [tailx, taily], + headcoords: [headx, heady], + }, + }); + + // adjust for constraints + moveX = 2; + moveY = 1; + headx += moveX; + heady += moveY; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied head"); + cy.window().then(async (win) => { + headx = -5; + heady = 7; + + win.callAction1({ + actionName: "movePoint", + componentName: "/head", + args: { x: headx, y: heady }, + }); + + // adjust for constraints + headx = -5; + heady = 6; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied tail"); + cy.window().then(async (win) => { + tailx = -3; + taily = -9; + + win.callAction1({ + actionName: "movePoint", + componentName: "/tail", + args: { x: tailx, y: taily }, + }); + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + + cy.log("move copied displacement"); + cy.window().then(async (win) => { + let displacementTailShiftx = -4; + let displacementTailShifty = -5; + + let displacementx = 2; + let displacementy = -3; + + headx = tailx + displacementx; + heady = taily + displacementy; + + let displacementheadx = displacementTailShiftx + displacementx; + let displacementheady = displacementTailShifty + displacementy; + + win.callAction1({ + actionName: "moveVector", + componentName: "/displacement", + args: { + tailcoords: [displacementTailShiftx, displacementTailShifty], + headcoords: [displacementheadx, displacementheady], + }, + }); + + // adjust for constraints + headx = Math.round(headx / 5) * 5; + heady = Math.round(heady / 3) * 3; + headx = headx === 0 ? 0 : headx; // change -0 to 0 + displacementx = headx - tailx; + displacementy = heady - taily; + + await testVectorCopiedHTD({ + headx, + heady, + tailx, + taily, + displacementTailShiftx, + displacementTailShifty, + }); + }); + }); }); From 2bc23c99ff6820325525b6abf476e8c506987162 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Fri, 21 Apr 2023 03:39:50 +0000 Subject: [PATCH 5/7] First attempt to get image unstuck from attractor when moved via keyboard --- src/Core/Core.js | 79 ++++++++++++++++++++++++++++++++++ src/Core/components/Image.js | 6 +++ src/Core/utils/graphical.js | 21 +++++++++ src/Viewer/renderers/image.jsx | 23 +++++++--- 4 files changed, 123 insertions(+), 6 deletions(-) diff --git a/src/Core/Core.js b/src/Core/Core.js index 2df99925ac..8b33cb0530 100644 --- a/src/Core/Core.js +++ b/src/Core/Core.js @@ -9357,6 +9357,7 @@ export default class Core { canSkipUpdatingRenderer = false, skipRendererUpdate = false, sourceInformation = {}, + attemptLargerMoveIfStuck, suppressToast = false, // temporary }) { if (this.flags.readOnly && !overrideReadOnly) { @@ -9465,6 +9466,84 @@ export default class Core { newStateVariableValuesProcessed.push(newStateVariableValues); + if (attemptLargerMoveIfStuck) { + // Check to see if state variable specified is unchanged. + // If so, attempt larger move + let cName = attemptLargerMoveIfStuck.componentName; + let vName = attemptLargerMoveIfStuck.stateVariable; + let origValue = attemptLargerMoveIfStuck.originalValue; + let limits = attemptLargerMoveIfStuck.limits; + + let newValue = await this._components[cName].stateValues[vName]; + + let noChange; + + if (newValue instanceof me.class && origValue instanceof me.class) { + noChange = newValue.equals(origValue); + } else if (Array.isArray(newValue) && Array.isArray(origValue)) { + noChange = + newValue.length === origValue.length && + newValue.every((v, i) => v === origValue[i]); + } else { + noChange = newValue === origValue; + } + + if (noChange) { + let reqValue = attemptLargerMoveIfStuck.requestedValue; + + if (origValue instanceof me.class && reqValue instanceof me.class) { + if ( + origValue.tree[0] === "vector" && + reqValue.tree[0] === "vector" && + origValue.tree.length === reqValue.tree.length + ) { + let origNums = origValue.tree.slice(1); + let reqNums = reqValue.tree.slice(1); + + if ( + origNums.every(Number.isFinite) && + reqNums.every(Number.isFinite) + ) { + for (let stepMult = 2; stepMult < 50; stepMult++) { + let newDesiredNums = origNums.map( + (v, i) => v + stepMult * (reqNums[i] - v), + ); + if (limits) { + newDesiredNums = newDesiredNums.map((v, i) => + Math.min(limits[i][1], Math.max(limits[i][0], v)), + ); + } + + let instruction = { + updateType: "updateValue", + componentName: cName, + stateVariable: vName, + value: me.fromAst(["vector", ...newDesiredNums]), + }; + + let newStateVariableValues = {}; + await this.requestComponentChanges({ + instruction, + workspace, + newStateVariableValues, + }); + + await this.executeUpdateStateVariables(newStateVariableValues); + + newStateVariableValuesProcessed.push(newStateVariableValues); + + let newValue = await this._components[cName].stateValues[vName]; + + if (!newValue.equals(origValue)) { + break; + } + } + } + } + } + } + } + // always update the renderers from the update instructions themselves, // as even if changes were prevented, the renderers need to be given that information // so they can revert if the showed the changes before hearing back from core diff --git a/src/Core/components/Image.js b/src/Core/components/Image.js index 9b3d94e0da..dcf09ddc89 100644 --- a/src/Core/components/Image.js +++ b/src/Core/components/Image.js @@ -454,6 +454,9 @@ export default class Image extends BlockComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -463,6 +466,9 @@ export default class Image extends BlockComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/utils/graphical.js b/src/Core/utils/graphical.js index b4feda4ade..14cb4ff65f 100644 --- a/src/Core/utils/graphical.js +++ b/src/Core/utils/graphical.js @@ -134,6 +134,9 @@ export async function moveGraphicalObjectWithAnchorAction({ y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -152,6 +155,23 @@ export async function moveGraphicalObjectWithAnchorAction({ components[3] = z; } if (transient) { + let attemptLargerMoveIfStuck; + if (viaKeyboard) { + let foundChange = + (x !== undefined && x != lastPosition[0]) || + (y !== undefined && y != lastPosition[1]) || + (z !== undefined && z != lastPosition[2]); + + if (foundChange) { + attemptLargerMoveIfStuck = { + componentName, + stateVariable: "anchor", + originalValue: me.fromAst(["vector", ...lastPosition]), + requestedValue: me.fromAst(components), + limits, + }; + } + } return await coreFunctions.performUpdate({ updateInstructions: [ { @@ -165,6 +185,7 @@ export async function moveGraphicalObjectWithAnchorAction({ actionId, sourceInformation, skipRendererUpdate, + attemptLargerMoveIfStuck, }); } else { return await coreFunctions.performUpdate({ diff --git a/src/Viewer/renderers/image.jsx b/src/Viewer/renderers/image.jsx index 4e4a156b2e..01fad9e07e 100644 --- a/src/Viewer/renderers/image.jsx +++ b/src/Viewer/renderers/image.jsx @@ -324,14 +324,25 @@ export default React.memo(function Image(props) { Math.max(yminAdjusted, calculatedY.current), ); + let args = { + x: calculatedX.current, + y: calculatedY.current, + transient: true, + skippable: true, + }; + + if (!viaPointer) { + args.viaKeyboard = true; + args.lastPosition = lastPositionFromCore.current; + args.limits = [ + [xminAdjusted, xmaxAdjusted], + [yminAdjusted, ymaxAdjusted], + ]; + } + callAction({ action: actions.moveImage, - args: { - x: calculatedX.current, - y: calculatedY.current, - transient: true, - skippable: true, - }, + args, }); newImageJXG.relativeCoords.setCoordinates( From 78116ccb0600c566c652c14d57e0377671335c19 Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Sun, 23 Apr 2023 03:45:07 +0000 Subject: [PATCH 6/7] extend to all components with anchor points --- src/Core/components/BooleanInput.js | 6 ++++++ src/Core/components/CallAction.js | 6 ++++++ src/Core/components/Label.js | 6 ++++++ src/Core/components/MMeMen.js | 6 ++++++ src/Core/components/Math.js | 6 ++++++ src/Core/components/MdMdnMrow.js | 6 ++++++ src/Core/components/Number.js | 6 ++++++ src/Core/components/Text.js | 6 ++++++ src/Core/components/TextInput.js | 6 ++++++ src/Core/components/TriggerSet.js | 6 ++++++ src/Core/components/UpdateValue.js | 6 ++++++ src/Core/utils/graphical.js | 6 +++--- src/Viewer/renderers/booleanInput.jsx | 23 +++++++++++++++++------ src/Viewer/renderers/button.jsx | 23 +++++++++++++++++------ src/Viewer/renderers/label.jsx | 23 +++++++++++++++++------ src/Viewer/renderers/math.jsx | 23 +++++++++++++++++------ src/Viewer/renderers/number.jsx | 23 +++++++++++++++++------ src/Viewer/renderers/text.jsx | 23 +++++++++++++++++------ src/Viewer/renderers/textInput.jsx | 23 +++++++++++++++++------ 19 files changed, 188 insertions(+), 45 deletions(-) diff --git a/src/Core/components/BooleanInput.js b/src/Core/components/BooleanInput.js index 0e5dab84d3..52bbeeb92d 100644 --- a/src/Core/components/BooleanInput.js +++ b/src/Core/components/BooleanInput.js @@ -244,6 +244,9 @@ export default class BooleanInput extends Input { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -253,6 +256,9 @@ export default class BooleanInput extends Input { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/CallAction.js b/src/Core/components/CallAction.js index 92e2497425..3cf2c04404 100644 --- a/src/Core/components/CallAction.js +++ b/src/Core/components/CallAction.js @@ -241,6 +241,9 @@ export default class CallAction extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -250,6 +253,9 @@ export default class CallAction extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/Label.js b/src/Core/components/Label.js index 8b5eb72ff6..8df830da6c 100644 --- a/src/Core/components/Label.js +++ b/src/Core/components/Label.js @@ -434,6 +434,9 @@ export default class Label extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -443,6 +446,9 @@ export default class Label extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/MMeMen.js b/src/Core/components/MMeMen.js index 1b9df49abf..28178da016 100644 --- a/src/Core/components/MMeMen.js +++ b/src/Core/components/MMeMen.js @@ -272,6 +272,9 @@ export class M extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -281,6 +284,9 @@ export class M extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/Math.js b/src/Core/components/Math.js index 56fffa160e..fd2e504ca0 100644 --- a/src/Core/components/Math.js +++ b/src/Core/components/Math.js @@ -2153,6 +2153,9 @@ export default class MathComponent extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -2162,6 +2165,9 @@ export default class MathComponent extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/MdMdnMrow.js b/src/Core/components/MdMdnMrow.js index 0d5843127c..93d8744b84 100644 --- a/src/Core/components/MdMdnMrow.js +++ b/src/Core/components/MdMdnMrow.js @@ -241,6 +241,9 @@ export class Md extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -250,6 +253,9 @@ export class Md extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/Number.js b/src/Core/components/Number.js index db261a3677..9f31a86108 100644 --- a/src/Core/components/Number.js +++ b/src/Core/components/Number.js @@ -1518,6 +1518,9 @@ export default class NumberComponent extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -1527,6 +1530,9 @@ export default class NumberComponent extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/Text.js b/src/Core/components/Text.js index 07d3acd998..e7360c6213 100644 --- a/src/Core/components/Text.js +++ b/src/Core/components/Text.js @@ -283,6 +283,9 @@ export default class Text extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -292,6 +295,9 @@ export default class Text extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/TextInput.js b/src/Core/components/TextInput.js index e86a21a178..4745309ee2 100644 --- a/src/Core/components/TextInput.js +++ b/src/Core/components/TextInput.js @@ -434,6 +434,9 @@ export default class Textinput extends Input { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -443,6 +446,9 @@ export default class Textinput extends Input { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/TriggerSet.js b/src/Core/components/TriggerSet.js index d23a90be22..6635037089 100644 --- a/src/Core/components/TriggerSet.js +++ b/src/Core/components/TriggerSet.js @@ -169,6 +169,9 @@ export default class triggerSet extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -178,6 +181,9 @@ export default class triggerSet extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/components/UpdateValue.js b/src/Core/components/UpdateValue.js index 5d68c17c77..40a86a44ba 100644 --- a/src/Core/components/UpdateValue.js +++ b/src/Core/components/UpdateValue.js @@ -415,6 +415,9 @@ export default class UpdateValue extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation = {}, skipRendererUpdate = false, @@ -424,6 +427,9 @@ export default class UpdateValue extends InlineComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, sourceInformation, skipRendererUpdate, diff --git a/src/Core/utils/graphical.js b/src/Core/utils/graphical.js index 14cb4ff65f..44f1ec0f17 100644 --- a/src/Core/utils/graphical.js +++ b/src/Core/utils/graphical.js @@ -155,7 +155,7 @@ export async function moveGraphicalObjectWithAnchorAction({ components[3] = z; } if (transient) { - let attemptLargerMoveIfStuck; + let largerMoveOptions; if (viaKeyboard) { let foundChange = (x !== undefined && x != lastPosition[0]) || @@ -163,7 +163,7 @@ export async function moveGraphicalObjectWithAnchorAction({ (z !== undefined && z != lastPosition[2]); if (foundChange) { - attemptLargerMoveIfStuck = { + largerMoveOptions = { componentName, stateVariable: "anchor", originalValue: me.fromAst(["vector", ...lastPosition]), @@ -185,7 +185,7 @@ export async function moveGraphicalObjectWithAnchorAction({ actionId, sourceInformation, skipRendererUpdate, - attemptLargerMoveIfStuck, + largerMoveOptions, }); } else { return await coreFunctions.performUpdate({ diff --git a/src/Viewer/renderers/booleanInput.jsx b/src/Viewer/renderers/booleanInput.jsx index 227d8f23fa..0162f5141b 100644 --- a/src/Viewer/renderers/booleanInput.jsx +++ b/src/Viewer/renderers/booleanInput.jsx @@ -284,14 +284,25 @@ export default React.memo(function BooleanInput(props) { Math.max(yminAdjusted, calculatedY.current), ); + let args = { + x: calculatedX.current, + y: calculatedY.current, + transient: true, + skippable: true, + }; + + if (!viaPointer) { + args.viaKeyboard = true; + args.lastPosition = lastPositionFromCore.current; + args.limits = [ + [xminAdjusted, xmaxAdjusted], + [yminAdjusted, ymaxAdjusted], + ]; + } + callAction({ action: actions.moveInput, - args: { - x: calculatedX.current, - y: calculatedY.current, - transient: true, - skippable: true, - }, + args, }); newInputJXG.relativeCoords.setCoordinates(JXG.COORDS_BY_USER, [0, 0]); diff --git a/src/Viewer/renderers/button.jsx b/src/Viewer/renderers/button.jsx index 86ed767fd9..df2a1a6a5c 100644 --- a/src/Viewer/renderers/button.jsx +++ b/src/Viewer/renderers/button.jsx @@ -206,14 +206,25 @@ export default React.memo(function ButtonComponent(props) { Math.max(yminAdjusted, calculatedY.current), ); + let args = { + x: calculatedX.current, + y: calculatedY.current, + transient: true, + skippable: true, + }; + + if (!viaPointer) { + args.viaKeyboard = true; + args.lastPosition = lastPositionFromCore.current; + args.limits = [ + [xminAdjusted, xmaxAdjusted], + [yminAdjusted, ymaxAdjusted], + ]; + } + callAction({ action: actions.moveButton, - args: { - x: calculatedX.current, - y: calculatedY.current, - transient: true, - skippable: true, - }, + args, }); newButtonJXG.relativeCoords.setCoordinates(JXG.COORDS_BY_USER, [0, 0]); diff --git a/src/Viewer/renderers/label.jsx b/src/Viewer/renderers/label.jsx index 95bf7f6f85..8066fa6e04 100644 --- a/src/Viewer/renderers/label.jsx +++ b/src/Viewer/renderers/label.jsx @@ -258,14 +258,25 @@ export default React.memo(function Label(props) { Math.max(yminAdjusted, calculatedY.current), ); + let args = { + x: calculatedX.current, + y: calculatedY.current, + transient: true, + skippable: true, + }; + + if (!viaPointer) { + args.viaKeyboard = true; + args.lastPosition = lastPositionFromCore.current; + args.limits = [ + [xminAdjusted, xmaxAdjusted], + [yminAdjusted, ymaxAdjusted], + ]; + } + callAction({ action: actions.moveLabel, - args: { - x: calculatedX.current, - y: calculatedY.current, - transient: true, - skippable: true, - }, + args, }); newLabelJXG.relativeCoords.setCoordinates(JXG.COORDS_BY_USER, [0, 0]); diff --git a/src/Viewer/renderers/math.jsx b/src/Viewer/renderers/math.jsx index 3c33734494..7bb78ac54c 100644 --- a/src/Viewer/renderers/math.jsx +++ b/src/Viewer/renderers/math.jsx @@ -278,14 +278,25 @@ export default React.memo(function MathComponent(props) { Math.max(yminAdjusted, calculatedY.current), ); + let args = { + x: calculatedX.current, + y: calculatedY.current, + transient: true, + skippable: true, + }; + + if (!viaPointer) { + args.viaKeyboard = true; + args.lastPosition = lastPositionFromCore.current; + args.limits = [ + [xminAdjusted, xmaxAdjusted], + [yminAdjusted, ymaxAdjusted], + ]; + } + callAction({ action: actions.moveMath, - args: { - x: calculatedX.current, - y: calculatedY.current, - transient: true, - skippable: true, - }, + args, }); newMathJXG.relativeCoords.setCoordinates(JXG.COORDS_BY_USER, [0, 0]); diff --git a/src/Viewer/renderers/number.jsx b/src/Viewer/renderers/number.jsx index 71b0c8e863..edb907586e 100644 --- a/src/Viewer/renderers/number.jsx +++ b/src/Viewer/renderers/number.jsx @@ -258,14 +258,25 @@ export default React.memo(function NumberComponent(props) { Math.max(yminAdjusted, calculatedY.current), ); + let args = { + x: calculatedX.current, + y: calculatedY.current, + transient: true, + skippable: true, + }; + + if (!viaPointer) { + args.viaKeyboard = true; + args.lastPosition = lastPositionFromCore.current; + args.limits = [ + [xminAdjusted, xmaxAdjusted], + [yminAdjusted, ymaxAdjusted], + ]; + } + callAction({ action: actions.moveNumber, - args: { - x: calculatedX.current, - y: calculatedY.current, - transient: true, - skippable: true, - }, + args, }); newNumberJXG.relativeCoords.setCoordinates(JXG.COORDS_BY_USER, [0, 0]); diff --git a/src/Viewer/renderers/text.jsx b/src/Viewer/renderers/text.jsx index b7c052372e..b5201dadb3 100644 --- a/src/Viewer/renderers/text.jsx +++ b/src/Viewer/renderers/text.jsx @@ -253,14 +253,25 @@ export default React.memo(function Text(props) { Math.max(yminAdjusted, calculatedY.current), ); + let args = { + x: calculatedX.current, + y: calculatedY.current, + transient: true, + skippable: true, + }; + + if (!viaPointer) { + args.viaKeyboard = true; + args.lastPosition = lastPositionFromCore.current; + args.limits = [ + [xminAdjusted, xmaxAdjusted], + [yminAdjusted, ymaxAdjusted], + ]; + } + callAction({ action: actions.moveText, - args: { - x: calculatedX.current, - y: calculatedY.current, - transient: true, - skippable: true, - }, + args, }); newTextJXG.relativeCoords.setCoordinates(JXG.COORDS_BY_USER, [0, 0]); diff --git a/src/Viewer/renderers/textInput.jsx b/src/Viewer/renderers/textInput.jsx index 043ebfe272..7f4fc78d53 100644 --- a/src/Viewer/renderers/textInput.jsx +++ b/src/Viewer/renderers/textInput.jsx @@ -409,14 +409,25 @@ export default function TextInput(props) { Math.max(yminAdjusted, calculatedY.current), ); + let args = { + x: calculatedX.current, + y: calculatedY.current, + transient: true, + skippable: true, + }; + + if (!viaPointer) { + args.viaKeyboard = true; + args.lastPosition = lastPositionFromCore.current; + args.limits = [ + [xminAdjusted, xmaxAdjusted], + [yminAdjusted, ymaxAdjusted], + ]; + } + callAction({ action: actions.moveInput, - args: { - x: calculatedX.current, - y: calculatedY.current, - transient: true, - skippable: true, - }, + args, }); newInputJXG.relativeCoords.setCoordinates(JXG.COORDS_BY_USER, [0, 0]); From e6bfc6e87893af7213e8a3a8a42e15efb04e6e5e Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Sun, 23 Apr 2023 03:47:59 +0000 Subject: [PATCH 7/7] refine keyboard escape, extend to point --- src/Core/Core.js | 263 +++++++++++++++++++++++---------- src/Core/components/Point.js | 29 ++++ src/Viewer/renderers/point.jsx | 51 +++++-- 3 files changed, 253 insertions(+), 90 deletions(-) diff --git a/src/Core/Core.js b/src/Core/Core.js index 8b33cb0530..36d57a6cc7 100644 --- a/src/Core/Core.js +++ b/src/Core/Core.js @@ -9357,7 +9357,7 @@ export default class Core { canSkipUpdatingRenderer = false, skipRendererUpdate = false, sourceInformation = {}, - attemptLargerMoveIfStuck, + largerMoveOptions, suppressToast = false, // temporary }) { if (this.flags.readOnly && !overrideReadOnly) { @@ -9466,82 +9466,13 @@ export default class Core { newStateVariableValuesProcessed.push(newStateVariableValues); - if (attemptLargerMoveIfStuck) { - // Check to see if state variable specified is unchanged. - // If so, attempt larger move - let cName = attemptLargerMoveIfStuck.componentName; - let vName = attemptLargerMoveIfStuck.stateVariable; - let origValue = attemptLargerMoveIfStuck.originalValue; - let limits = attemptLargerMoveIfStuck.limits; - - let newValue = await this._components[cName].stateValues[vName]; - - let noChange; - - if (newValue instanceof me.class && origValue instanceof me.class) { - noChange = newValue.equals(origValue); - } else if (Array.isArray(newValue) && Array.isArray(origValue)) { - noChange = - newValue.length === origValue.length && - newValue.every((v, i) => v === origValue[i]); - } else { - noChange = newValue === origValue; - } - - if (noChange) { - let reqValue = attemptLargerMoveIfStuck.requestedValue; - - if (origValue instanceof me.class && reqValue instanceof me.class) { - if ( - origValue.tree[0] === "vector" && - reqValue.tree[0] === "vector" && - origValue.tree.length === reqValue.tree.length - ) { - let origNums = origValue.tree.slice(1); - let reqNums = reqValue.tree.slice(1); - - if ( - origNums.every(Number.isFinite) && - reqNums.every(Number.isFinite) - ) { - for (let stepMult = 2; stepMult < 50; stepMult++) { - let newDesiredNums = origNums.map( - (v, i) => v + stepMult * (reqNums[i] - v), - ); - if (limits) { - newDesiredNums = newDesiredNums.map((v, i) => - Math.min(limits[i][1], Math.max(limits[i][0], v)), - ); - } - - let instruction = { - updateType: "updateValue", - componentName: cName, - stateVariable: vName, - value: me.fromAst(["vector", ...newDesiredNums]), - }; - - let newStateVariableValues = {}; - await this.requestComponentChanges({ - instruction, - workspace, - newStateVariableValues, - }); - - await this.executeUpdateStateVariables(newStateVariableValues); - - newStateVariableValuesProcessed.push(newStateVariableValues); - - let newValue = await this._components[cName].stateValues[vName]; - - if (!newValue.equals(origValue)) { - break; - } - } - } - } - } - } + if (largerMoveOptions) { + await this.attemptLargerMoveIfUnchanged( + largerMoveOptions, + workspace, + newStateVariableValuesProcessed, + sourceInformation, + ); } // always update the renderers from the update instructions themselves, @@ -9699,6 +9630,184 @@ export default class Core { } } + // Check to see if state variable specified is unchanged. + // If so, attempt larger move + async attemptLargerMoveIfUnchanged( + largerMoveOptions, + workspace, + newStateVariableValuesProcessed, + sourceInformation, + ) { + let componentName = largerMoveOptions.componentName; + let stateVariable = largerMoveOptions.stateVariable; + let originalValue = largerMoveOptions.originalValue; + let limits = largerMoveOptions.limits; + + // unchangedTolerance specifies what is considered unchanged + let unchangedTolerance = 1e-2; + if (limits) { + let scale = + (Math.abs(limits[0][1] - limits[0][0]) + + Math.abs(limits[1][1] - limits[1][0])) / + 2; + if (Number.isFinite(scale)) { + unchangedTolerance = scale * 1e-3; + } + } + + let newValue = await this._components[componentName].state[stateVariable] + .value; + + let haveArray = false; + + // the function to determine if we have a large enough change in the state variable + let checkIfUnchanged; + + if (newValue instanceof me.class && originalValue instanceof me.class) { + checkIfUnchanged = (expr1, expr2, tol) => + expr1.equals(expr2, { + allowed_error_in_numbers: tol, + allowed_error_is_absolute: true, + }); + } else if ( + Array.isArray(newValue) && + newValue.every((x) => x instanceof me.class) && + Array.isArray(originalValue) && + originalValue.every((x) => x instanceof me.class) + ) { + checkIfUnchanged = (expr1, expr2, tol) => + expr1.length === expr2.length && + expr1.every((v, i) => { + return v.equals(expr2[i], { + allowed_error_in_numbers: tol, + allowed_error_is_absolute: true, + }); + }); + + haveArray = true; + } else { + // only implemented for math-expressions or arrray of math-expressions so far + return; + } + + if (!checkIfUnchanged(newValue, originalValue, unchangedTolerance)) { + // if the value changed by more than unchangedTolerance, we're done + return; + } + + // Note: since we typically will have found a change and won't continue past here, + // we want to keep the above code as minimal as possible + + let requestedValue = largerMoveOptions.requestedValue; + + // so far, we've implemented this algorithm only for vectors of numbers + // and arrays of numerical math-expressions + let originalNums, requestedNums; + + if (haveArray) { + originalNums = originalValue.map((x) => x.tree); + requestedNums = requestedValue.map((x) => x.tree); + } else { + if ( + originalValue.tree[0] === "vector" && + requestedValue.tree[0] === "vector" + ) { + originalNums = originalValue.tree.slice(1); + requestedNums = requestedValue.tree.slice(1); + } else { + // if have single math-expression, only implemented for vectors + // (could implement for all vectorOperators if we find a case where it is needed) + return; + } + } + + if ( + !( + originalNums.every(Number.isFinite) && + requestedNums.every(Number.isFinite) + ) + ) { + // only implemented for numerical components + return; + } + + let previousDesiredNums = requestedNums; + + // try steps up to about 100 times larger, doubling each time so takes at most 8 tries + // (it takes about 100 unconstrained steps to cross width of full size graph) + for (let stepMult = 2; stepMult <= 128; stepMult *= 2) { + ////////////////////////////////////////////////// + // calculate a value of stateVariables that is further away from originalValue + ////////////////////////////////////////////////// + + let newDesiredNums = originalNums.map( + (v, i) => v + stepMult * (requestedNums[i] - v), + ); + if (limits) { + newDesiredNums = newDesiredNums.map((v, i) => + Math.min(limits[i][1], Math.max(limits[i][0], v)), + ); + } + + if (newDesiredNums.every((v, i) => previousDesiredNums[i] === v)) { + // the desired position isn't changing at all, + // so either we ran into a limit + // or the requestedValue was the same as the originalValue + return; + } + + previousDesiredNums = newDesiredNums; + + let value = haveArray + ? newDesiredNums.map((x) => me.fromAst(x)) + : me.fromAst(["vector", ...newDesiredNums]); + + ////////////////////////////////////////////////// + // repeat the base procedure from performUpdate + // to try to change stateVariable to this new value + ////////////////////////////////////////////////// + + let instruction = { + updateType: "updateValue", + componentName: componentName, + stateVariable: stateVariable, + value, + }; + + let newStateVariableValues = {}; + await this.requestComponentChanges({ + instruction, + workspace, + newStateVariableValues, + }); + + await this.executeUpdateStateVariables(newStateVariableValues); + + newStateVariableValuesProcessed.push(newStateVariableValues); + + ////////////////////////////////////////////////// + // check if we've succeeded to obtain a value sufficiently far away from originalValue + ////////////////////////////////////////////////// + + let newValue = await this._components[componentName].stateValues[ + stateVariable + ]; + + if (!checkIfUnchanged(newValue, originalValue, unchangedTolerance)) { + // found a large enough movement, so we are unstuck + + let componentSourceInformation = sourceInformation[componentName]; + if (!componentSourceInformation) { + componentSourceInformation = sourceInformation[componentName] = {}; + } + + componentSourceInformation.largeMoveModification = true; + + return; + } + } + } + async updateAllChangedRenderers(sourceInformation = {}, actionId) { let componentNamesToUpdate = [ ...this.updateInfo.componentsToUpdateRenderers, diff --git a/src/Core/components/Point.js b/src/Core/components/Point.js index f24f27a70d..c7864c4523 100644 --- a/src/Core/components/Point.js +++ b/src/Core/components/Point.js @@ -1275,7 +1275,11 @@ export default class Point extends GraphicalComponent { y, z, transient, + viaKeyboard, + lastPosition, + limits, actionId, + sourceDetails, sourceInformation = {}, skipRendererUpdate = false, }) { @@ -1290,6 +1294,28 @@ export default class Point extends GraphicalComponent { components[2] = me.fromAst(z); } if (transient) { + let largerMoveOptions; + if (viaKeyboard) { + let foundChange = + (x !== undefined && x != lastPosition[0]) || + (y !== undefined && y != lastPosition[1]) || + (z !== undefined && z != lastPosition[2]); + + if (foundChange) { + // for largerMove algorithm, expect requestedValue to be an array + let requestedValue = []; + for (let ind in components) { + requestedValue[ind] = components[ind]; + } + largerMoveOptions = { + componentName: this.componentName, + stateVariable: "xs", + originalValue: lastPosition.map((x) => me.fromAst(x)), + requestedValue, + limits, + }; + } + } return await this.coreFunctions.performUpdate({ updateInstructions: [ { @@ -1297,12 +1323,14 @@ export default class Point extends GraphicalComponent { componentName: this.componentName, stateVariable: "xs", value: components, + sourceDetails, }, ], transient, actionId, sourceInformation, skipRendererUpdate, + largerMoveOptions, }); } else { return await this.coreFunctions.performUpdate({ @@ -1312,6 +1340,7 @@ export default class Point extends GraphicalComponent { componentName: this.componentName, stateVariable: "xs", value: components, + sourceDetails, }, ], actionId, diff --git a/src/Viewer/renderers/point.jsx b/src/Viewer/renderers/point.jsx index bffca059a7..a982b50772 100644 --- a/src/Viewer/renderers/point.jsx +++ b/src/Viewer/renderers/point.jsx @@ -320,22 +320,42 @@ export default React.memo(function Point(props) { calculatedX.current = Math.min(xmax, Math.max(xmin, calculatedX.current)); calculatedY.current = Math.min(ymax, Math.max(ymin, calculatedY.current)); + let args = { + x: calculatedX.current, + y: calculatedY.current, + transient: true, + skippable: true, + }; + + if (!viaPointer) { + args.viaKeyboard = true; + args.lastPosition = lastPositionFromCore.current; + args.limits = [ + [xmin, xmax], + [ymin, ymax], + ]; + args.sourceDetails = { viaKeyboard: true }; + } + callAction({ action: actions.movePoint, - args: { - x: calculatedX.current, - y: calculatedY.current, - transient: true, - skippable: true, - }, + args, }); - let shadowX = Math.min(xmax, Math.max(xmin, newShadowPointJXG.X())); - let shadowY = Math.min(ymax, Math.max(ymin, newShadowPointJXG.Y())); - newShadowPointJXG.coords.setCoordinates(JXG.COORDS_BY_USER, [ - shadowX, - shadowY, - ]); + if (viaPointer) { + let shadowX = Math.min(xmax, Math.max(xmin, newShadowPointJXG.X())); + let shadowY = Math.min(ymax, Math.max(ymin, newShadowPointJXG.Y())); + newShadowPointJXG.coords.setCoordinates(JXG.COORDS_BY_USER, [ + shadowX, + shadowY, + ]); + } else { + // don't use shadow point with keyboard + newShadowPointJXG.coords.setCoordinates( + JXG.COORDS_BY_USER, + lastPositionFromCore.current, + ); + } newPointJXG.coords.setCoordinates( JXG.COORDS_BY_USER, @@ -421,7 +441,12 @@ export default React.memo(function Point(props) { let y = SVs.numericalXs?.[1]; pointJXG.current.coords.setCoordinates(JXG.COORDS_BY_USER, [x, y]); - if (!dragged.current) { + + // don't use shadow point with keyboard + if ( + !dragged.current || + sourceOfUpdate.sourceInformation?.[name]?.viaKeyboard + ) { shadowPointJXG.current.coords.setCoordinates(JXG.COORDS_BY_USER, [ x, y,