diff --git a/cypress/e2e/DoenetML/tagSpecific/circle.cy.js b/cypress/e2e/DoenetML/tagSpecific/circle.cy.js index 1bd52a0e4d..f33098e91b 100644 --- a/cypress/e2e/DoenetML/tagSpecific/circle.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/circle.cy.js @@ -14416,9 +14416,22 @@ 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; + // 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", @@ -14488,8 +14501,15 @@ 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; + // 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", @@ -14525,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({ @@ -14560,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 - + @@ -14601,8 +14620,9 @@ describe("Circle Tag Tests", function () { 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 + // 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", @@ -14672,8 +14692,11 @@ 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 = -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", @@ -14743,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 - + @@ -14768,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, @@ -14776,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", @@ -14795,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(); @@ -14804,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, @@ -14816,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", @@ -14828,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(); @@ -14836,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, @@ -14844,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) - - + + @@ -14874,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.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.numericalCenter[0], - ).closeTo(newCenter[0], 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(newCenter[1], 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, + ]); }); }); cy.log("move center point"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - - 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; - + let desiredHeight = 7; + let actualHeight = 7; // since moving center itself await win.callAction1({ actionName: "movePoint", componentName: "/centerPoint", - args: { x: newCenter[0], y: newCenter[1] }, + args: { x: 8, y: desiredHeight }, }); - let r = Math.abs(t1x - t2x); - + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(${nInDOM(8)}`); 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.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.numericalCenter[0], - ).closeTo(newCenter[0], 1e-12); - expect( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ).closeTo(newCenter[1], 1e-12); + 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 first through point"); + cy.log("move circle below x-axis"); cy.window().then(async (win) => { - t1x = 6; - t1y = 3; + 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: "movePoint", - componentName: "/TP1", - args: { x: t1x, y: t1y }, + actionName: "moveCircle", + componentName: "/_circle1", + args: { center: [4, desiredHeight] }, }); - let r = Math.abs(t1x - t2x); - - cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); + 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.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([ + 4, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq( + -actualHeight, ); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + 4, + actualHeight, + ]); }); }); - cy.log("move second through point under first through point"); + cy.log("move circle back up with center point"); cy.window().then(async (win) => { - t2x = 5; - t2y = -3; + let desiredHeight = 4; + let actualHeight = 4; // since moving point itself await win.callAction1({ actionName: "movePoint", - componentName: "/TP2", - args: { x: t2x, y: t2y }, + componentName: "/centerPoint", + args: { x: 1, y: desiredHeight }, }); - let r = Math.abs(t1x - t2x); - - cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); + 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.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( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).false; - expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).false; - }); + 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 second through point close enough to make circle"); + cy.log("move circle"); cy.window().then(async (win) => { - t2y = 1.5; + let desiredHeight = 5; + let actualHeight = -2; await win.callAction1({ - actionName: "movePoint", - componentName: "/TP2", - args: { x: t2x, y: t2y }, + 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(stateVariables["/_circle1"].stateValues.center).eqls([ + -3, + actualHeight, + ]); + expect(stateVariables["/_circle1"].stateValues.radius).eq(1); + expect(stateVariables["/centerPoint"].stateValues.coords).eqls([ + "vector", + -3, + 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, - ); + cy.log("move center point"); + cy.window().then(async (win) => { + let desiredHeight = 7; + let actualHeight = 9; // since moving center itself + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: 8, y: desiredHeight }, + }); - expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[0], - ), - ).true; - expect( - Number.isFinite( - stateVariables["/_circle1"].stateValues.numericalCenter[1], - ), - ).true; + 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.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) + + `, @@ -15278,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, @@ -15304,7 +15314,6 @@ describe("Circle Tag Tests", function () { t2x, t2y, ]); - cy.get(cesc("#\\/radiusNumber") + " .mjx-mrow").should( "contain.text", nInDOM(r), @@ -15331,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, @@ -15374,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); }); }); @@ -15392,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; @@ -15406,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) => { @@ -15449,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); }); }); @@ -15471,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)); @@ -15518,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)); @@ -15580,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)); @@ -15646,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) - + @@ -15677,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, @@ -15702,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; @@ -15732,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(); @@ -15781,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; @@ -15806,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(); @@ -15855,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; }); }); @@ -15873,20 +15884,13 @@ 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, @@ -15922,19 +15926,11 @@ describe("Circle Tag Tests", function () { t2y, 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 second through point"); + cy.log("move second through point under first through point"); cy.window().then(async (win) => { - t2x = -7; t2y = -9; await win.callAction1({ actionName: "movePoint", @@ -15942,16 +15938,74 @@ describe("Circle Tag Tests", function () { args: { x: t2x, y: t2y }, }); - t1x = t2x - 1; - let r = Math.sqrt(Math.pow(t1x - t2x, 2) + Math.pow(t1y - t2y, 2)) / 2; + let r = t1x; - let cnx = (t1x + t2x) / 2; - let cny = (t1y + t2y) / 2; + cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); - 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.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 to the right"); + cy.window().then(async (win) => { + t2x = 8; + await win.callAction1({ + actionName: "movePoint", + componentName: "/TP2", + args: { x: t2x, y: t2y }, + }); + + t1x = t2x - 1; + let r = t1x; + + cy.get(cesc("#\\/radiusNumber")).should("contain.text", nInDOM(r)); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); @@ -15992,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) + + - `, @@ -16025,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, @@ -16045,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, @@ -16059,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", @@ -16086,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; @@ -16101,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", @@ -16112,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), @@ -16138,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, @@ -16161,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], @@ -16181,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; @@ -16196,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", @@ -16207,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), @@ -16233,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, @@ -16256,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], @@ -16284,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], @@ -16311,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, @@ -16334,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); }); }); @@ -16366,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], @@ -16393,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, @@ -16416,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], @@ -16508,363 +16584,372 @@ 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(); - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + let numericalCenter = + stateVariables["/_circle1"].stateValues.numericalCenter; + let cnx = numericalCenter[0]; + let cny = numericalCenter[1]; - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); + let dx = -1, + dy = -2; + cnx += dx; + cny += dy; + t1x += dx; + t1y += dy; + t2x += dx; + t2y += dy; + t3x += dx; + t3y += dy; - 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)})`, - ); - }); + let r = stateVariables["/_circle1"].stateValues.radius; - 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, + ); - cy.log("move flipped point"); - cy.window().then(async (win) => { - await win.callAction1({ - actionName: "movePoint", - componentName: "/_point1", - args: { x: -3, y: -5 }, - }); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], + ).closeTo(t1x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], + ).closeTo(t1y, 1e-12); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(-5)},${nInDOM(-3)})`, - ); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], + ).closeTo(t2x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], + ).closeTo(t2y, 1e-12); - 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( + 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 center point"); + cy.log("move first through point"); cy.window().then(async (win) => { + t1x = 6; + t1y = 3; await win.callAction1({ actionName: "movePoint", - componentName: "/centerPoint", - args: { x: 1, y: -4 }, + 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(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; - cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], + ).closeTo(t1x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], + ).closeTo(t1y, 1e-12); - 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", `(_,_)`); - }); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], + ).closeTo(t2x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], + ).closeTo(t2y, 1e-12); - cy.log("enter point for center"); - cy.get(cesc("#\\/c") + " textarea").type("(2,1){enter}", { force: true }); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], + ).closeTo(t3x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], + ).closeTo(t3y, 1e-12); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(2)},${nInDOM(1)})`, - ); + 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, + ); - 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( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).true; + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).true; + }); }); - cy.log(`move circle`); + cy.log("move second through point"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); + t2x = -7; + t2y = -9; await win.callAction1({ - actionName: "moveCircle", - componentName: "/circ", - args: { center: [-7, 2] }, + actionName: "movePoint", + 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(-7)},${nInDOM(2)})`, + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, ); 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); - }); - }); + expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) + .true; - cy.log("change point for center"); - cy.get(cesc("#\\/c") + " textarea").type( - "{end}{leftArrow}{backspace}-4{enter}", - { force: true }, - ); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], + ).closeTo(t1x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], + ).closeTo(t1y, 1e-12); - 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); + 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 circle2`); + cy.log("move third through point"); cy.window().then(async (win) => { + t3x = 1; + t3y = -2; await win.callAction1({ - actionName: "moveCircle", - componentName: "/circ2", - args: { center: [6, 9] }, + actionName: "movePoint", + componentName: "/TP3", + args: { x: t3x, y: t3y }, }); - cy.get(cesc("#\\/centerPoint2")).should( + t1x = t3x - 1; + t2x = t1x - 1; + + cy.get(cesc("#\\/TP1")).should( "contain.text", - `(${nInDOM(6)},${nInDOM(9)})`, + `(${nInDOM(Math.trunc(t1x * 100) / 100)}`, ); + 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); - }); - }); + expect(Number.isFinite(stateVariables["/_circle1"].stateValues.radius)) + .true; - cy.log("center undefined again"); - cy.get(cesc("#\\/c") + " textarea").type( - "{ctrl+home}{shift+end}{backspace}{enter}", - { force: true }, - ); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][0], + ).closeTo(t1x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[0][1], + ).closeTo(t1y, 1e-12); - cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(_,_)`); - 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); - }); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][0], + ).closeTo(t2x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[1][1], + ).closeTo(t2y, 1e-12); - cy.log("enter new point for center"); - cy.get(cesc("#\\/c") + " textarea").type("(5,4){enter}", { force: true }); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][0], + ).closeTo(t3x, 1e-12); + expect( + stateVariables["/_circle1"].stateValues.numericalThroughPoints[2][1], + ).closeTo(t3y, 1e-12); - cy.get(cesc("#\\/centerPoint2")).should( - "contain.text", - `(${nInDOM(5)},${nInDOM(4)})`, - ); + 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, + ); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([5, 4]); - expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); - expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([5, 4]); - expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[0], + ), + ).true; + expect( + Number.isFinite( + stateVariables["/_circle1"].stateValues.numericalCenter[1], + ), + ).true; + }); }); }); - it("overwrite attributes on copy", () => { + it("essential center can combine coordinates", () => { cy.window().then(async (win) => { win.postMessage( { doenetML: ` a - - - -

Change radius:

-

Change center:

- - - (3,4) - - - -

Change radius:

-

Change center:

- - - (7,7) - - - -

Change radius:

-

Change center:

- - - + + + (, + ) + + -

Set radius radius:

-

Change radius:

-

Change center:

+ `, }, @@ -16874,327 +16959,655 @@ describe("Circle Tag Tests", function () { cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load - cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "1"); - cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(0,0)"); - cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "1"); - cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( - "have.text", - "(3,4)", - ); - cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "5"); - cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( - "have.text", - "(3,4)", - ); - cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( - "have.text", - "(0,0)", - ); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); - cy.log("move original circle"); + 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: "/c", - args: { center: [-1, 2] }, + componentName: "/_circle1", + args: { center: [-7, 2] }, }); - }); - cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "1"); - cy.get(cesc("#\\/cc") + " .mq-editable-field").should( - "have.text", - "(−1,2)", - ); - cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "1"); - cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( - "have.text", - "(3,4)", - ); - cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "5"); - cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( - "have.text", - "(3,4)", - ); - cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( - "have.text", - "(−1,2)", - ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(-7)},${nInDOM(2)})`, + ); - cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(await stateVariables["/P"].stateValues.xs).eqls([3, 4]); - expect(await stateVariables["/Q"].stateValues.xs).eqls([7, 7]); + 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, + ]); + }); }); - cy.log("enter non-numeric radius and center for original circle"); - cy.get(cesc("#\\/rc") + " textarea").type("{end}+x{enter}", { - force: true, - }); - cy.get(cesc("#\\/cc") + " textarea").type( - "{end}{leftArrow}{backspace}y{enter}", - { force: true }, - ); + cy.log("move flipped point"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/_point1", + args: { x: -3, y: -5 }, + }); - cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "1+x"); - cy.get(cesc("#\\/cc") + " .mq-editable-field") - .invoke("text") - .then((text) => { - expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("(−1,y)"); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(-5)},${nInDOM(-3)})`, + ); + + 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, + ]); }); - cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "1+x"); - cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( - "have.text", - "(3,4)", - ); - cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "5"); - cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( - "have.text", - "(3,4)", - ); - cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( - "have.text", - "(−1,y)", - ); + }); + cy.log("move center point"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(await stateVariables["/P"].stateValues.xs).eqls([3, 4]); - expect(await stateVariables["/Q"].stateValues.xs).eqls([7, 7]); - }); + await win.callAction1({ + actionName: "movePoint", + componentName: "/centerPoint", + args: { x: 1, y: -4 }, + }); - cy.log( - "set radius and center for original circle back to number using other components", - ); - cy.get(cesc("#\\/rc1") + " textarea").type( - "{ctrl+home}{shift+end}{backspace}2{enter}", - { force: true }, - ); - cy.get(cesc("#\\/cc3") + " textarea").type( - "{end}{leftArrow}{backspace}{backspace}{backspace}{backspace}4,5{enter}", - { force: true }, - ); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(1)},${nInDOM(-4)})`, + ); - cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "2"); - cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); - cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "2"); - cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( - "have.text", - "(3,4)", - ); - cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "5"); - cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( - "have.text", - "(3,4)", - ); - cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/cc3") + " .mq-editable-field") - .invoke("text") - .then((text) => { - expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("(4,5)"); + 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) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(await stateVariables["/P"].stateValues.xs).eqls([3, 4]); - expect(await stateVariables["/Q"].stateValues.xs).eqls([7, 7]); + win.postMessage( + { + doenetML: ` + a +

Center:

+ + + + + + + + + + `, + }, + "*", + ); }); - cy.log("move point P and set radius of second circle"); + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + cy.window().then(async (win) => { - await win.callAction1({ - actionName: "movePoint", - componentName: "/P", - args: { x: -5, y: 2 }, - }); + 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.get(cesc("#\\/rc1") + " textarea").type("{end}{backspace}4{enter}", { - force: true, - }); + cy.log("enter point for center"); + cy.get(cesc("#\\/c") + " textarea").type("(2,1){enter}", { force: true }); - cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); - cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); - cy.get(cesc("#\\/rc1") + " .mq-editable-field") - .invoke("text") - .then((text) => { - expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("4"); - }); - cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( - "have.text", - "(−5,2)", - ); - cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "13"); - cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( - "have.text", - "(−5,2)", - ); - cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( - "have.text", - "(4,5)", + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(2)},${nInDOM(1)})`, ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(await stateVariables["/P"].stateValues.xs).eqls([-5, 2]); - expect(await stateVariables["/Q"].stateValues.xs).eqls([7, 7]); + 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 point Q"); + cy.log(`move circle`); cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); await win.callAction1({ - actionName: "movePoint", - componentName: "/Q", - args: { x: 3, y: 8 }, + actionName: "moveCircle", + componentName: "/circ", + args: { center: [-7, 2] }, }); - }); + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(-7)},${nInDOM(2)})`, + ); - cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); - cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); - cy.get(cesc("#\\/rc1") + " .mq-editable-field") - .invoke("text") - .then((text) => { - expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("4"); + 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.get(cesc("#\\/cc1") + " .mq-editable-field").should( - "have.text", - "(−5,2)", + }); + + cy.log("change point for center"); + cy.get(cesc("#\\/c") + " textarea").type( + "{end}{leftArrow}{backspace}-4{enter}", + { force: true }, ); - cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "10"); - cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( - "have.text", - "(−5,2)", + + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(-7)},${nInDOM(-4)})`, ); - cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); - cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( - "have.text", - "(4,5)", + 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( + "{ctrl+home}{shift+end}{backspace}{enter}", + { force: true }, ); + cy.get(cesc("#\\/centerPoint2")).should("contain.text", `(_,_)`); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(await stateVariables["/P"].stateValues.xs).eqls([-5, 2]); - expect(await stateVariables["/Q"].stateValues.xs).eqls([3, 8]); + 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.log("set radius of third circle"); + cy.log("enter new point for center"); + cy.get(cesc("#\\/c") + " textarea").type("(5,4){enter}", { force: true }); - cy.get(cesc("#\\/rc2") + " textarea").type( - "{end}{backspace}{backspace}5{enter}", - { force: true }, + cy.get(cesc("#\\/centerPoint2")).should( + "contain.text", + `(${nInDOM(5)},${nInDOM(4)})`, ); - cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); - cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); - cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "4"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables["/circ"].stateValues.numericalCenter).eqls([5, 4]); + expect(stateVariables["/circ"].stateValues.numericalRadius).eq(1); + expect(stateVariables["/circ2"].stateValues.numericalCenter).eqls([5, 4]); + expect(stateVariables["/circ2"].stateValues.numericalRadius).eq(1); + }); + }); + + it("overwrite attributes on copy", () => { + cy.window().then(async (win) => { + win.postMessage( + { + doenetML: ` + a + + + + +

Change radius:

+

Change center:

+ + + (3,4) + + + +

Change radius:

+

Change center:

+ + + (7,7) + + + +

Change radius:

+

Change center:

+ + + + + +

Set radius radius:

+

Change radius:

+

Change center:

+ + `, + }, + "*", + ); + }); + + cy.get(cesc("#\\/_text1")).should("have.text", "a"); // to wait for page to load + + cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "1"); + cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(0,0)"); + cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "1"); cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( "have.text", - "(−5,2)", + "(3,4)", ); - cy.get(cesc("#\\/rc2") + " .mq-editable-field") - .invoke("text") - .then((text) => { - expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("5"); - }); + cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "5"); cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( "have.text", - "(−5,2)", + "(3,4)", ); cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( "have.text", - "(4,5)", + "(0,0)", ); + cy.log("move original circle"); cy.window().then(async (win) => { - let stateVariables = await win.returnAllStateVariables1(); - expect(await stateVariables["/P"].stateValues.xs).eqls([-5, 2]); - expect(await stateVariables["/Q"].stateValues.xs).eqls([-1, 5]); + await win.callAction1({ + actionName: "moveCircle", + componentName: "/c", + args: { center: [-1, 2] }, + }); }); - cy.log("set center of third circle"); - - cy.get(cesc("#\\/cc2") + " textarea").type( - "{end}{leftArrow}{backspace}{backspace}{backspace}{backspace}5,-3{enter}", - { force: true }, + cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "1"); + cy.get(cesc("#\\/cc") + " .mq-editable-field").should( + "have.text", + "(−1,2)", ); - - cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); - cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); - cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "1"); cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( "have.text", - "(5,−3)", + "(3,4)", + ); + cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "5"); + cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( + "have.text", + "(3,4)", ); - cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "10"); - cy.get(cesc("#\\/cc2") + " .mq-editable-field") - .invoke("text") - .then((text) => { - expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("(5,−3)"); - }); cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( "have.text", - "(4,5)", + "(−1,2)", ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(await stateVariables["/P"].stateValues.xs).eqls([5, -3]); - expect(await stateVariables["/Q"].stateValues.xs).eqls([-1, 5]); + expect(await stateVariables["/P"].stateValues.xs).eqls([3, 4]); + expect(await stateVariables["/Q"].stateValues.xs).eqls([7, 7]); }); - cy.log("set radius of fourth circle"); - - cy.get(cesc("#\\/src3") + " textarea").type("{end}{backspace}9{enter}", { + cy.log("enter non-numeric radius and center for original circle"); + cy.get(cesc("#\\/rc") + " textarea").type("{end}+x{enter}", { force: true, }); + cy.get(cesc("#\\/cc") + " textarea").type( + "{end}{leftArrow}{backspace}y{enter}", + { force: true }, + ); - cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); - cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); - cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "1+x"); + cy.get(cesc("#\\/cc") + " .mq-editable-field") + .invoke("text") + .then((text) => { + expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("(−1,y)"); + }); + cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "1+x"); cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( "have.text", - "(5,−3)", + "(3,4)", ); - cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "10"); + cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "5"); cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( "have.text", - "(5,−3)", + "(3,4)", ); - cy.get(cesc("#\\/src3") + " .mq-editable-field") - .invoke("text") - .then((text) => { - expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("9"); - }); - cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "9"); + cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( "have.text", - "(4,5)", + "(−1,y)", ); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(await stateVariables["/P"].stateValues.xs).eqls([5, -3]); - expect(await stateVariables["/Q"].stateValues.xs).eqls([-1, 5]); + expect(await stateVariables["/P"].stateValues.xs).eqls([3, 4]); + expect(await stateVariables["/Q"].stateValues.xs).eqls([7, 7]); }); - cy.log("move and change radius of fourth circle"); - - cy.window().then(async (win) => { - await win.callAction1({ - actionName: "moveCircle", + cy.log( + "set radius and center for original circle back to number using other components", + ); + cy.get(cesc("#\\/rc1") + " textarea").type( + "{ctrl+home}{shift+end}{backspace}2{enter}", + { force: true }, + ); + cy.get(cesc("#\\/cc3") + " textarea").type( + "{end}{leftArrow}{backspace}{backspace}{backspace}{backspace}4,5{enter}", + { force: true }, + ); + + cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "2"); + cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); + cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "2"); + cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( + "have.text", + "(3,4)", + ); + cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "5"); + cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( + "have.text", + "(3,4)", + ); + cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/cc3") + " .mq-editable-field") + .invoke("text") + .then((text) => { + expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("(4,5)"); + }); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(await stateVariables["/P"].stateValues.xs).eqls([3, 4]); + expect(await stateVariables["/Q"].stateValues.xs).eqls([7, 7]); + }); + + cy.log("move point P and set radius of second circle"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/P", + args: { x: -5, y: 2 }, + }); + }); + + cy.get(cesc("#\\/rc1") + " textarea").type("{end}{backspace}4{enter}", { + force: true, + }); + + cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); + cy.get(cesc("#\\/rc1") + " .mq-editable-field") + .invoke("text") + .then((text) => { + expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("4"); + }); + cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( + "have.text", + "(−5,2)", + ); + cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "13"); + cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( + "have.text", + "(−5,2)", + ); + cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( + "have.text", + "(4,5)", + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(await stateVariables["/P"].stateValues.xs).eqls([-5, 2]); + expect(await stateVariables["/Q"].stateValues.xs).eqls([7, 7]); + }); + + cy.log("move point Q"); + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "movePoint", + componentName: "/Q", + args: { x: 3, y: 8 }, + }); + }); + + cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); + cy.get(cesc("#\\/rc1") + " .mq-editable-field") + .invoke("text") + .then((text) => { + expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("4"); + }); + cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( + "have.text", + "(−5,2)", + ); + cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "10"); + cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( + "have.text", + "(−5,2)", + ); + cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( + "have.text", + "(4,5)", + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(await stateVariables["/P"].stateValues.xs).eqls([-5, 2]); + expect(await stateVariables["/Q"].stateValues.xs).eqls([3, 8]); + }); + + cy.log("set radius of third circle"); + + cy.get(cesc("#\\/rc2") + " textarea").type( + "{end}{backspace}{backspace}5{enter}", + { force: true }, + ); + + cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); + cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( + "have.text", + "(−5,2)", + ); + cy.get(cesc("#\\/rc2") + " .mq-editable-field") + .invoke("text") + .then((text) => { + expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("5"); + }); + cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( + "have.text", + "(−5,2)", + ); + cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( + "have.text", + "(4,5)", + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(await stateVariables["/P"].stateValues.xs).eqls([-5, 2]); + expect(await stateVariables["/Q"].stateValues.xs).eqls([-1, 5]); + }); + + cy.log("set center of third circle"); + + cy.get(cesc("#\\/cc2") + " textarea").type( + "{end}{leftArrow}{backspace}{backspace}{backspace}{backspace}5,-3{enter}", + { force: true }, + ); + + cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); + cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( + "have.text", + "(5,−3)", + ); + cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "10"); + cy.get(cesc("#\\/cc2") + " .mq-editable-field") + .invoke("text") + .then((text) => { + expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("(5,−3)"); + }); + cy.get(cesc("#\\/src3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "3"); + cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( + "have.text", + "(4,5)", + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(await stateVariables["/P"].stateValues.xs).eqls([5, -3]); + expect(await stateVariables["/Q"].stateValues.xs).eqls([-1, 5]); + }); + + cy.log("set radius of fourth circle"); + + cy.get(cesc("#\\/src3") + " textarea").type("{end}{backspace}9{enter}", { + force: true, + }); + + cy.get(cesc("#\\/rc") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/cc") + " .mq-editable-field").should("have.text", "(4,5)"); + cy.get(cesc("#\\/rc1") + " .mq-editable-field").should("have.text", "4"); + cy.get(cesc("#\\/cc1") + " .mq-editable-field").should( + "have.text", + "(5,−3)", + ); + cy.get(cesc("#\\/rc2") + " .mq-editable-field").should("have.text", "10"); + cy.get(cesc("#\\/cc2") + " .mq-editable-field").should( + "have.text", + "(5,−3)", + ); + cy.get(cesc("#\\/src3") + " .mq-editable-field") + .invoke("text") + .then((text) => { + expect(text.replace(/[\s\u200B-\u200D\uFEFF]/g, "")).equal("9"); + }); + cy.get(cesc("#\\/rc3") + " .mq-editable-field").should("have.text", "9"); + cy.get(cesc("#\\/cc3") + " .mq-editable-field").should( + "have.text", + "(4,5)", + ); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(await stateVariables["/P"].stateValues.xs).eqls([5, -3]); + expect(await stateVariables["/Q"].stateValues.xs).eqls([-1, 5]); + }); + + cy.log("move and change radius of fourth circle"); + + cy.window().then(async (win) => { + await win.callAction1({ + actionName: "moveCircle", componentName: "/c3", args: { center: [3, 8] }, }); @@ -17718,4 +18131,7821 @@ 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, 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; + + 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: "/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 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 two points, one point constrained, allow flexible motion", () => { + 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 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: "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; + + 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; + + 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, 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 circle up and to the right"); + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + let cnx = stateVariables["/graph3/circle"].stateValues.numericalCenter[0]; + let cny = stateVariables["/graph3/circle"].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 += desireddx; + t3y += desireddy; + + 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, + ); + 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.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 desireddx = -5, + desireddy = -2.2; + + 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; + + await win.callAction1({ + actionName: "moveCircle", + componentName: "/graph3/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, + ); + 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.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, + ); + 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, + ); + }); + }); + }); + + 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); + }); + }); + }); + + it("circle through three points, two points 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 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, + ); + 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.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 desireddx = -4.9, + desireddy = -2.2; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = -6; + let dy = -2; + + 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("#\\/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, + ); + 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.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.1, + desireddy = -2.9; + + let desiredcnx = cnx + desireddx; + let desiredcny = cny + desireddy; + + let dx = 6; + let dy = -2; + + 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: "/graph4/circle", + args: { center: [desiredcnx, desiredcny] }, + }); + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( + "contain.text", + `(${nInDOM(Math.trunc(t1x * 1000) / 1000)}`, + ); + cy.get(cesc("#\\/tp1") + " .mjx-mrow").should( + "contain.text", + `${nInDOM(Math.trunc(t1y * 1000) / 1000)}`, + ); + cy.get(cesc("#\\/tp2") + " .mjx-mrow").should( + "contain.text", + `(${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["/_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, + ); + }); + }); + }); }); 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, + }); + }); + }); }); diff --git a/src/Core/Core.js b/src/Core/Core.js index 861e95e4b2..36d57a6cc7 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, @@ -9346,6 +9357,7 @@ export default class Core { canSkipUpdatingRenderer = false, skipRendererUpdate = false, sourceInformation = {}, + largerMoveOptions, suppressToast = false, // temporary }) { if (this.flags.readOnly && !overrideReadOnly) { @@ -9454,6 +9466,15 @@ export default class Core { newStateVariableValuesProcessed.push(newStateVariableValues); + if (largerMoveOptions) { + await this.attemptLargerMoveIfUnchanged( + largerMoveOptions, + workspace, + newStateVariableValuesProcessed, + sourceInformation, + ); + } + // 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 @@ -9609,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/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/Circle.js b/src/Core/components/Circle.js index 1da9beca2a..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; @@ -1237,7 +1244,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 +1593,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 +1608,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 +2535,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 +2544,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 +2578,22 @@ export default class Circle extends Curve { }); } + // Note: we set skipRendererUpdate to true + // so that we can make further adjustments before the renderers are updated 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 +2606,175 @@ export default class Circle extends Curve { }, }); } + + // unless allowFlexibleMotion is set + // we attempt to keep the radius of the circle fixed + // even if one of the points defining it is constrained + + 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 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({ 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/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/Line.js b/src/Core/components/Line.js index 5b6e023e20..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,8 +1801,10 @@ 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) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: [ { updateType: "updateValue", @@ -1807,10 +1816,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 +1830,7 @@ export default class Line extends GraphicalComponent { ], actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -1834,6 +1843,87 @@ export default class Line extends GraphicalComponent { }, }); } + + // 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 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 + // 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 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..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,8 +703,10 @@ 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) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: [ { componentName: this.componentName, @@ -710,10 +719,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 +734,7 @@ export default class LineSegment extends GraphicalComponent { ], actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -739,6 +748,90 @@ export default class LineSegment extends GraphicalComponent { }, }); } + + // 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 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 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/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/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/Core/components/Polyline.js b/src/Core/components/Polyline.js index 4598cc65c3..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,8 +622,10 @@ 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) { - return await this.coreFunctions.performUpdate({ + await this.coreFunctions.performUpdate({ updateInstructions: [ { updateType: "updateValue", @@ -629,10 +638,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 +653,7 @@ export default class Polyline extends GraphicalComponent { ], actionId, sourceInformation, - skipRendererUpdate, + skipRendererUpdate: true, event: { verb: "interacted", object: { @@ -657,6 +666,114 @@ export default class Polyline extends GraphicalComponent { }, }); } + + // 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 + + 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) { + // 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]), + ); + } + } + + 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..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,21 +1563,23 @@ 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) { - 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 +1593,94 @@ export default class Ray extends GraphicalComponent { }, }); } + + // 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 + // 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 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..f7c14f6071 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 @@ -1370,22 +1370,22 @@ 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) { - 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,132 @@ export default class Rectangle extends Polygon { }, }); } + + // 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 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 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. + + 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..13966b6df2 100644 --- a/src/Core/components/RegularPolygon.js +++ b/src/Core/components/RegularPolygon.js @@ -1742,4 +1742,361 @@ 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 }); + } + + // 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 = {}; + 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; + + 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, + }, + ]; + + // 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, + 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, + }, + }, + }); + } + + // 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 + + let resultingCenter = await this.stateValues.centerComponents; + + let resultingDirectionWithRadius = await this.stateValues + .directionWithRadius; + let resultingVertex1 = [ + resultingCenter[0] + resultingDirectionWithRadius[0], + resultingCenter[1] + resultingDirectionWithRadius[1], + ]; + + 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, + ); + + 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/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/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/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/components/Vector.js b/src/Core/components/Vector.js index 0022f2fae4..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,21 +2333,23 @@ 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) { - 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 +2363,89 @@ export default class Vector extends GraphicalComponent { }, }); } + + // 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. + + 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({ diff --git a/src/Core/utils/graphical.js b/src/Core/utils/graphical.js index b4feda4ade..44f1ec0f17 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 largerMoveOptions; + if (viaKeyboard) { + let foundChange = + (x !== undefined && x != lastPosition[0]) || + (y !== undefined && y != lastPosition[1]) || + (z !== undefined && z != lastPosition[2]); + + if (foundChange) { + largerMoveOptions = { + 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, + 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/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( 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/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, 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]);