diff --git a/cypress/e2e/AssignedActivity/creditAchievedMenu.cy.js b/cypress/e2e/AssignedActivity/creditAchievedMenu.cy.js
index b586c6f757..d6648f7de2 100644
--- a/cypress/e2e/AssignedActivity/creditAchievedMenu.cy.js
+++ b/cypress/e2e/AssignedActivity/creditAchievedMenu.cy.js
@@ -41,13 +41,13 @@ describe('Credit achieved menu tests', function () {
Enter x: x
`
- cy.createActivity({courseId,doenetId,parentDoenetId:courseId,pageDoenetId, doenetML});
+ cy.createActivity({ courseId, doenetId, parentDoenetId: courseId, pageDoenetId, doenetML });
cy.visit(`http://localhost/course?tool=navigation&courseId=${courseId}`)
- cy.get('.navigationRow').should('have.length',1); //Need this to wait for the row to appear
+ cy.get('.navigationRow').should('have.length', 1); //Need this to wait for the row to appear
cy.get('.navigationRow').eq(0).get('.navigationColumn1').click();
-
+
cy.get('[data-test="Assign Activity"]').click();
cy.get('[data-test="Unassign Activity"]').should('be.visible')
@@ -60,7 +60,7 @@ describe('Credit achieved menu tests', function () {
cy.get('[data-test="Item 1 Credit"]').should('have.text', '0%')
- cy.get('#\\/ans textarea').type("x{enter}", {force: true})
+ cy.get('#\\/ans textarea').type("x{enter}", { force: true })
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
cy.get('[data-test="New Attempt"]').click();
@@ -77,27 +77,29 @@ describe('Credit achieved menu tests', function () {
Enter x: x
`
-const doenetML2 = `
+ const doenetML2 = `
No questions here
`;
-const doenetML3 = `
+ const doenetML3 = `
Enter y: y
`
-const doenetML4 = `No questions here, either
`;
+ const doenetML4 = `No questions here, either
`;
- cy.createMultipageActivity({ courseId, doenetId, parentDoenetId: courseId,
+ cy.createMultipageActivity({
+ courseId, doenetId, parentDoenetId: courseId,
pageDoenetId1: pageDoenetId, pageDoenetId2, pageDoenetId3, pageDoenetId4,
- doenetML1, doenetML2, doenetML3, doenetML4 });
+ doenetML1, doenetML2, doenetML3, doenetML4
+ });
cy.visit(`http://localhost/course?tool=navigation&courseId=${courseId}`)
- cy.get('.navigationRow').should('have.length',1); //Need this to wait for the row to appear
+ cy.get('.navigationRow').should('have.length', 1); //Need this to wait for the row to appear
cy.get('.navigationRow').eq(0).get('.navigationColumn1').click();
-
+
cy.get('[data-test="Assign Activity"]').click();
cy.get('[data-test="Unassign Activity"]').should('be.visible')
@@ -113,8 +115,8 @@ const doenetML4 = `No questions here, either
`;
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
cy.get('[data-test="Item 4 Credit"]').should('have.text', '0%')
cy.get('[data-test="Assignment Percent"]').should('have.text', '0%')
-
- cy.get('#page1\\/ans textarea').type("x{enter}", {force: true})
+
+ cy.get('#page1\\/ans textarea').type("x{enter}", { force: true })
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 2 Credit"]').should('have.text', '0%')
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
@@ -141,7 +143,7 @@ const doenetML4 = `No questions here, either
`;
cy.get('[data-test="Item 4 Credit"]').should('have.text', '0%')
cy.get('[data-test="Assignment Percent"]').should('have.text', '50%')
- cy.get('#page3\\/ans textarea').type("y{enter}", {force: true})
+ cy.get('#page3\\/ans textarea').type("y{enter}", { force: true })
cy.get('[data-test="Item 3 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
@@ -169,37 +171,39 @@ const doenetML4 = `No questions here, either
`;
`
-const doenetML2 = `
+ const doenetML2 = `
No questions here
`;
-const doenetML3 = `
+ const doenetML3 = `
Enter y: y
`
-const doenetML4 = `
+ const doenetML4 = `
No questions here, either
`;
- cy.createMultipageActivity({ courseId, doenetId, parentDoenetId: courseId,
+ cy.createMultipageActivity({
+ courseId, doenetId, parentDoenetId: courseId,
pageDoenetId1: pageDoenetId, pageDoenetId2, pageDoenetId3, pageDoenetId4,
- doenetML1, doenetML2, doenetML3, doenetML4 });
+ doenetML1, doenetML2, doenetML3, doenetML4
+ });
cy.visit(`http://localhost/course?tool=navigation&courseId=${courseId}`)
- cy.get('.navigationRow').should('have.length',1); //Need this to wait for the row to appear
+ cy.get('.navigationRow').should('have.length', 1); //Need this to wait for the row to appear
cy.get('.navigationRow').eq(0).get('.navigationColumn1').click();
-
+
cy.get('[data-test="Assign Activity"]').click();
cy.get('[data-test="Unassign Activity"]').should('be.visible')
-
+ cy.wait(100);
cy.get('[data-test="Paginate"').click();
cy.wait(100) //TODO: need the UI to let us know this was successful
@@ -214,8 +218,8 @@ const doenetML4 = `
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
cy.get('[data-test="Item 4 Credit"]').should('have.text', '0%')
cy.get('[data-test="Assignment Percent"]').should('have.text', '0%')
-
- cy.get('#page1\\/ans textarea').type("x{enter}", {force: true})
+
+ cy.get('#page1\\/ans textarea').type("x{enter}", { force: true })
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 2 Credit"]').should('have.text', '0%')
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
@@ -242,7 +246,7 @@ const doenetML4 = `
cy.get('[data-test="Item 4 Credit"]').should('have.text', '0%')
cy.get('[data-test="Assignment Percent"]').should('have.text', '50%')
- cy.get('#page3\\/ans textarea').type("y{enter}", {force: true})
+ cy.get('#page3\\/ans textarea').type("y{enter}", { force: true })
cy.get('[data-test="Item 3 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
@@ -270,28 +274,30 @@ const doenetML4 = `
Enter x: x
`
-const doenetML2 = `
+ const doenetML2 = `
No questions here
`;
-const doenetML3 = `
+ const doenetML3 = `
Enter y: y
`
-const doenetML4 = `No questions here, either
`;
+ const doenetML4 = `No questions here, either
`;
- cy.createMultipageActivity({ courseId, doenetId, parentDoenetId: courseId,
+ cy.createMultipageActivity({
+ courseId, doenetId, parentDoenetId: courseId,
pageDoenetId1: pageDoenetId, pageDoenetId2, pageDoenetId3, pageDoenetId4,
- doenetML1, doenetML2, doenetML3, doenetML4 });
+ doenetML1, doenetML2, doenetML3, doenetML4
+ });
cy.visit(`http://localhost/course?tool=navigation&courseId=${courseId}`)
- cy.get('.navigationRow').should('have.length',1); //Need this to wait for the row to appear
+ cy.get('.navigationRow').should('have.length', 1); //Need this to wait for the row to appear
cy.get('.navigationRow').eq(0).get('.navigationColumn1').click();
- cy.get('[data-test="Item Weights"]').clear().type("2 0 1 0", {force: true}).blur()
+ cy.get('[data-test="Item Weights"]').clear().type("2 0 1 0", { force: true }).blur()
cy.wait(100) //TODO: need the UI to let us know this was successful
cy.get('[data-test="Assign Activity"]').click();
@@ -310,8 +316,8 @@ const doenetML4 = `No questions here, either
`;
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
cy.get('[data-test="Item 4 Credit"]').should('have.text', 'Not started')
cy.get('[data-test="Assignment Percent"]').should('have.text', '0%')
-
- cy.get('#page1\\/ans textarea').type("x{enter}", {force: true})
+
+ cy.get('#page1\\/ans textarea').type("x{enter}", { force: true })
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 2 Credit"]').should('have.text', 'Not started')
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
@@ -338,7 +344,7 @@ const doenetML4 = `No questions here, either
`;
cy.get('[data-test="Item 4 Credit"]').should('have.text', 'Not started')
cy.get('[data-test="Assignment Percent"]').should('have.text', '66.7%')
- cy.get('#page3\\/ans textarea').type("y{enter}", {force: true})
+ cy.get('#page3\\/ans textarea').type("y{enter}", { force: true })
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 2 Credit"]').should('have.text', 'Complete')
@@ -365,26 +371,28 @@ const doenetML4 = `No questions here, either
`;
Enter x: x
`
-const doenetML2 = `
+ const doenetML2 = `
Enter y: y
`;
-const doenetML3 = `
+ const doenetML3 = `
Enter z: z
`
- cy.createMultipageActivity({ courseId, doenetId, parentDoenetId: courseId,
+ cy.createMultipageActivity({
+ courseId, doenetId, parentDoenetId: courseId,
pageDoenetId1: pageDoenetId, pageDoenetId2, pageDoenetId3,
- doenetML1, doenetML2, doenetML3 });
+ doenetML1, doenetML2, doenetML3
+ });
cy.visit(`http://localhost/course?tool=navigation&courseId=${courseId}`)
- cy.get('.navigationRow').should('have.length',1); //Need this to wait for the row to appear
+ cy.get('.navigationRow').should('have.length', 1); //Need this to wait for the row to appear
cy.get('.navigationRow').eq(0).get('.navigationColumn1').click();
-
+
cy.get('[data-test="Assign Activity"]').click();
cy.get('[data-test="Unassign Activity"]').should('be.visible')
@@ -403,8 +411,8 @@ const doenetML3 = `
cy.get('[data-test="Item 2 Credit"]').should('have.text', '0%')
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
cy.get('[data-test="Assignment Percent"]').should('have.text', '0%')
-
- cy.get('#page1\\/ans textarea').type("x{enter}", {force: true})
+
+ cy.get('#page1\\/ans textarea').type("x{enter}", { force: true })
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 2 Credit"]').should('have.text', '0%')
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
@@ -419,7 +427,7 @@ const doenetML3 = `
cy.url().should('match', /#page3$/)
- cy.get('#page3\\/ans textarea').type("z{enter}", {force: true})
+ cy.get('#page3\\/ans textarea').type("z{enter}", { force: true })
cy.get('[data-test="Item 3 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
@@ -434,7 +442,7 @@ const doenetML3 = `
cy.url().should('match', /#page2$/)
- cy.get('#page2\\/ans textarea').type("y{enter}", {force: true})
+ cy.get('#page2\\/ans textarea').type("y{enter}", { force: true })
cy.get('[data-test="Item 2 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
@@ -459,31 +467,34 @@ const doenetML3 = `
`
-const doenetML2 = `
+ const doenetML2 = `
Enter y: y
`;
-const doenetML3 = `
+ const doenetML3 = `
Enter z: z
`
- cy.createMultipageActivity({ courseId, doenetId, parentDoenetId: courseId,
+ cy.createMultipageActivity({
+ courseId, doenetId, parentDoenetId: courseId,
pageDoenetId1: pageDoenetId, pageDoenetId2, pageDoenetId3,
- doenetML1, doenetML2, doenetML3 });
+ doenetML1, doenetML2, doenetML3
+ });
cy.visit(`http://localhost/course?tool=navigation&courseId=${courseId}`)
- cy.get('.navigationRow').should('have.length',1); //Need this to wait for the row to appear
+ cy.get('.navigationRow').should('have.length', 1); //Need this to wait for the row to appear
cy.get('.navigationRow').eq(0).get('.navigationColumn1').click();
-
+
cy.get('[data-test="Assign Activity"]').click();
cy.get('[data-test="Unassign Activity"]').should('be.visible')
+ cy.wait(100)
cy.get('[data-test="Paginate"').click();
cy.wait(100) //TODO: need the UI to let us know this was successful
@@ -508,8 +519,8 @@ const doenetML3 = `
cy.get('[data-test="Item 2 Credit"]').should('have.text', '0%')
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
cy.get('[data-test="Assignment Percent"]').should('have.text', '0%')
-
- cy.get('#page1\\/ans textarea').type("x{enter}", {force: true})
+
+ cy.get('#page1\\/ans textarea').type("x{enter}", { force: true })
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 2 Credit"]').should('have.text', '0%')
cy.get('[data-test="Item 3 Credit"]').should('have.text', '0%')
@@ -523,7 +534,7 @@ const doenetML3 = `
cy.url().should('match', /#page3$/)
- cy.get('#page3\\/ans textarea').type("z{enter}", {force: true})
+ cy.get('#page3\\/ans textarea').type("z{enter}", { force: true })
cy.get('[data-test="Item 3 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
@@ -536,7 +547,7 @@ const doenetML3 = `
cy.url().should('match', /#page2$/)
- cy.get('#page2\\/ans textarea').type("y{enter}", {force: true})
+ cy.get('#page2\\/ans textarea').type("y{enter}", { force: true })
cy.get('[data-test="Item 2 Credit"]').should('have.text', '100%')
cy.get('[data-test="Item 1 Credit"]').should('have.text', '100%')
diff --git a/cypress/e2e/AssignedActivity/multipageActivities.cy.js b/cypress/e2e/AssignedActivity/multipageActivities.cy.js
index e2816de5e2..e3c91fb05e 100644
--- a/cypress/e2e/AssignedActivity/multipageActivities.cy.js
+++ b/cypress/e2e/AssignedActivity/multipageActivities.cy.js
@@ -176,6 +176,7 @@ describe('Multipage activity tests', function () {
cy.get('[data-test="Assign Activity"]').click();
cy.get('[data-test="Unassign Activity"]').should('be.visible')
+ cy.wait(100)
cy.get('[data-test="Paginate"').click();
cy.wait(100) //TODO: need the UI to let us know this was successful
@@ -1138,6 +1139,7 @@ describe('Multipage activity tests', function () {
cy.get('[data-test="Assign Activity"]').click();
cy.get('[data-test="Unassign Activity"]').should('be.visible')
+ cy.wait(100);
cy.signin({ userId: studentUserId })
diff --git a/cypress/e2e/DoenetML/tagSpecific/rectangle.cy.js b/cypress/e2e/DoenetML/tagSpecific/rectangle.cy.js
index d170e0a03d..672cd8f407 100644
--- a/cypress/e2e/DoenetML/tagSpecific/rectangle.cy.js
+++ b/cypress/e2e/DoenetML/tagSpecific/rectangle.cy.js
@@ -744,6 +744,314 @@ describe('Rectangle Tag Tests', function () {
});
+ it('draggable, vertices draggable', () => {
+ cy.window().then(async (win) => {
+ win.postMessage({
+ doenetML: `
+
+
+
+ To wait:
+ draggable:
+ vertices draggable:
+ corner vertices: $p.vertex1 $p.vertex3
+ `}, "*");
+ });
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'false')
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(1,3)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,7)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([1, 3]);
+ expect((stateVariables['/p'].stateValues.vertices)[2]).eqls([5, 7]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+ cy.log('cannot move single vertex')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: { 0: [4, 7] }
+ }
+ })
+ })
+
+
+ // wait for core to process click
+ cy.get('#\\/bi').click()
+ cy.get('#\\/bi2').should('have.text', 'true')
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'false')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(1,3)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,7)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([1, 3]);
+ expect((stateVariables['/p'].stateValues.vertices)[2]).eqls([5, 7]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+
+
+ cy.log('cannot move all vertices')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: [[4, 7], [8, 10], [1, 9], [3, 2]]
+ }
+ })
+ })
+
+
+ // wait for core to process click
+ cy.get('#\\/bi').click()
+ cy.get('#\\/bi2').should('have.text', 'false')
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'false')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(1,3)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,7)')
+
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([1, 3]);
+ expect((stateVariables['/p'].stateValues.vertices)[2]).eqls([5, 7]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+
+ cy.log('only vertices draggable')
+
+ cy.get('#\\/verticesDraggable').click()
+ cy.get('#\\/vd2').should('have.text', 'true')
+
+
+ cy.log('can move single vertex')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: { 0: [4, 7] }
+ }
+ })
+ })
+
+
+ cy.get("#\\/pvert .mjx-mrow").should('contain.text', '(4,7)')
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'true')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(4,7)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,7)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([4, 7]);
+ expect((stateVariables['/p'].stateValues.vertices)[2]).eqls([5, 7]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(true);
+ })
+
+
+
+ cy.log('cannot move all vertices')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: [[3, 8], [8, 10], [1, 9], [3, 2]]
+ }
+ })
+ })
+
+
+ // wait for core to process click
+ cy.get('#\\/bi').click()
+ cy.get('#\\/bi2').should('have.text', 'true')
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'true')
+
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(4,7)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,7)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([4, 7]);
+ expect((stateVariables['/p'].stateValues.vertices)[2]).eqls([5, 7]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(true);
+ })
+
+
+
+ cy.log('vertices and polygon draggable')
+
+ cy.get('#\\/draggable').click()
+ cy.get('#\\/d2').should('have.text', 'true')
+
+
+ cy.log('can move single vertex')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: { 2: [-3, 2] }
+ }
+ })
+ })
+
+
+ cy.get("#\\/pvert .mjx-mrow").should('contain.text', '(−3,2)')
+
+ cy.get("#\\/d2").should('have.text', 'true')
+ cy.get("#\\/vd2").should('have.text', 'true')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(4,7)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(−3,2)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([4, 7]);
+ expect((stateVariables['/p'].stateValues.vertices)[2]).eqls([-3, 2]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(true);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(true);
+ })
+
+
+
+ cy.log('can move all vertices')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: [[3, 8], [5, 8], [5, 1], [3, 1]]
+ }
+ })
+ })
+
+
+ cy.get("#\\/pvert .mjx-mrow").should('contain.text', '(3,8)')
+
+
+ cy.get("#\\/d2").should('have.text', 'true')
+ cy.get("#\\/vd2").should('have.text', 'true')
+
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(3,8)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,1)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([3, 8]);
+ expect((stateVariables['/p'].stateValues.vertices)[2]).eqls([5, 1]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(true);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(true);
+ })
+
+
+ cy.log('polygon but not vertices draggable')
+
+ cy.get('#\\/verticesDraggable').click()
+ cy.get('#\\/vd2').should('have.text', 'false')
+
+
+ cy.log('cannot move single vertex')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: { 2: [9, 3] }
+ }
+ })
+ })
+
+ // wait for core to process click
+ cy.get('#\\/bi').click()
+ cy.get('#\\/bi2').should('have.text', 'false')
+
+
+ cy.get("#\\/d2").should('have.text', 'true')
+ cy.get("#\\/vd2").should('have.text', 'false')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(3,8)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,1)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([3, 8]);
+ expect((stateVariables['/p'].stateValues.vertices)[2]).eqls([5, 1]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(true);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+
+
+ cy.log('can move all vertices')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: [[-4, 1], [3, 1], [3, 7], [-4, 7]]
+ }
+ })
+ })
+
+
+ cy.get("#\\/pvert .mjx-mrow").should('contain.text', '(−4,1)')
+
+
+ cy.get("#\\/d2").should('have.text', 'true')
+ cy.get("#\\/vd2").should('have.text', 'false')
+
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(−4,1)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(3,7)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([-4, 1]);
+ expect((stateVariables['/p'].stateValues.vertices)[2]).eqls([3, 7]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(true);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+
+
+ })
+
+
});
function setupScene({ rectangleProperties, rectangleChildren }) {
diff --git a/cypress/e2e/DoenetML/tagSpecific/regularPolygon.cy.js b/cypress/e2e/DoenetML/tagSpecific/regularPolygon.cy.js
new file mode 100644
index 0000000000..50d5d37e55
--- /dev/null
+++ b/cypress/e2e/DoenetML/tagSpecific/regularPolygon.cy.js
@@ -0,0 +1,1622 @@
+
+describe('Regular Polygon Tag Tests', function () {
+
+ beforeEach(() => {
+ cy.clearIndexedDB();
+ cy.visit('/cypressTest')
+
+ })
+
+ it('regular polygon with no parameters (gives triangle)', () => {
+
+ setupScene({
+ attributes: {},
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [1, 0],
+ center: [0, 0],
+ conservedWhenChangeNvertices: "circumradius"
+ });
+
+ });
+
+ it('specify area for square', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "4",
+ area: "100"
+ },
+ });
+
+ runTests({
+ nVertices: 4,
+ vertex1: [Math.sqrt(2) * 5, 0],
+ center: [0, 0],
+ conservedWhenChangeNvertices: "area"
+ });
+
+ });
+
+ it('specify sidelength, center for pentegon', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "5",
+ sideLength: "2",
+ center: "(4,2)"
+ },
+ });
+
+ runTests({
+ nVertices: 5,
+ vertex1: [4 + 2 / (2 * Math.sin(Math.PI / 5)), 2],
+ center: [4, 2],
+ conservedWhenChangeNvertices: "sideLength"
+ });
+
+ });
+
+ it('specify inRadius, center for hexagon', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "6",
+ inRadius: "3",
+ center: "(-2,5)"
+ },
+ });
+
+ runTests({
+ nVertices: 6,
+ vertex1: [-2 + 3 / (Math.cos(Math.PI / 6)), 5],
+ center: [-2, 5],
+ conservedWhenChangeNvertices: "inradius"
+ });
+
+ });
+
+ it('specify apothem for heptagon', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "7",
+ apothem: "4",
+ },
+ });
+
+ runTests({
+ nVertices: 7,
+ vertex1: [0 + 4 / (Math.cos(Math.PI / 7)), 0],
+ center: [0, 0],
+ conservedWhenChangeNvertices: "inradius",
+ abbreviated: true,
+ });
+
+ });
+
+ it('specify perimeter, center for octagon', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "8",
+ perimeter: "20",
+ center: "(-4,7)"
+ },
+ });
+
+ runTests({
+ nVertices: 8,
+ vertex1: [-4 + 20 / 8 / (2 * Math.sin(Math.PI / 8)), 7],
+ center: [-4, 7],
+ conservedWhenChangeNvertices: "perimeter",
+ abbreviated: true,
+ });
+
+ });
+
+ it('specify circumradius, center for triangle', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ circumradius: "6",
+ center: "(-5,8)"
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [-5 + 6, 8],
+ center: [-5, 8],
+ conservedWhenChangeNvertices: "circumradius"
+ });
+
+ });
+
+ it('specify radius for square', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "4",
+ radius: "7",
+ center: "(-6,-2)"
+ },
+ });
+
+ runTests({
+ nVertices: 4,
+ vertex1: [-6 + 7, -2],
+ center: [-6, -2],
+ conservedWhenChangeNvertices: "circumradius",
+ abbreviated: true,
+ });
+
+ });
+
+ it('specify center for pentagon', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "5",
+ center: "(-5,-3)"
+ },
+ });
+
+ runTests({
+ nVertices: 5,
+ vertex1: [-5 + 1, -3],
+ center: [-5, -3],
+ conservedWhenChangeNvertices: "circumradius"
+ });
+
+ });
+
+ it('specify one vertex for square', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: 4,
+ vertices: "(2,-5)"
+ },
+ });
+
+ runTests({
+ nVertices: 4,
+ vertex1: [2, -5],
+ center: [1, -5],
+ conservedWhenChangeNvertices: "circumradius"
+ });
+
+ });
+
+ it('specify two vertices for pentagon', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "5",
+ vertices: "(2,-5) (5,1)"
+ },
+ });
+
+ let nVertices = 5;
+
+ let vertex1 = [2, -5];
+ let vertex2 = [5, 1];
+
+ let sideVector = [vertex2[0] - vertex1[0], vertex2[1] - vertex1[1]];
+ let midpoint = [(vertex1[0] + vertex2[0]) / 2, (vertex1[1] + vertex2[1]) / 2];
+ let sideLength = Math.sqrt(sideVector[0] ** 2 + sideVector[1] ** 2);
+ let inradius = sideLength / (2 * Math.tan(Math.PI / nVertices));
+
+ let inradiusDirection = [-sideVector[1] / sideLength, sideVector[0] / sideLength];
+
+ let center = [midpoint[0] + inradiusDirection[0] * inradius, midpoint[1] + inradiusDirection[1] * inradius];
+
+
+ runTests({
+ nVertices,
+ vertex1,
+ center,
+ conservedWhenChangeNvertices: "twoVertices"
+ });
+
+ });
+
+ it('specify center and one vertex for triangle', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ vertices: "(2,-5)",
+ center: "(-1,-3)"
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [2, -5],
+ center: [-1, -3],
+ conservedWhenChangeNvertices: "circumradius"
+ });
+
+ });
+
+ it('specify center and two vertices for triangle, ignore second vertex', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ vertices: "(2,-5) (10,12)",
+ center: "(-1,-3)"
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [2, -5],
+ center: [-1, -3],
+ conservedWhenChangeNvertices: "circumradius",
+ abbreviated: true,
+ });
+
+ });
+
+ it('specify center and vertex for triangle, ignore all size attributes', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ vertices: "(2,-5)",
+ center: "(-1,-3)",
+ circumradius: "11",
+ inradius: "3",
+ sideLength: "5",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [2, -5],
+ center: [-1, -3],
+ conservedWhenChangeNvertices: "circumradius",
+ abbreviated: true,
+ });
+
+ })
+
+ it('specify center and circumradius for triangle, ignore all other size attributes', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ center: "(-1,-3)",
+ circumradius: "11",
+ inradius: "3",
+ sideLength: "5",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [10, -3],
+ center: [-1, -3],
+ conservedWhenChangeNvertices: "circumradius",
+ abbreviated: true,
+ });
+
+
+ });
+
+ it('specify vertex and circumradius for triangle, ignore all other size attributes', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ vertices: "(2,-5)",
+ circumradius: "11",
+ inradius: "3",
+ sideLength: "5",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [2, -5],
+ center: [-9, -5],
+ conservedWhenChangeNvertices: "circumradius",
+ abbreviated: true,
+ });
+
+
+ });
+
+ it('specify two vertices for triangle, ingnore all size attributes', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ vertices: "(2,-5) (5,1)",
+ circumradius: "11",
+ inradius: "3",
+ sideLength: "5",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ let nVertices = 3;
+
+ let vertex1 = [2, -5];
+ let vertex2 = [5, 1];
+
+ let sideVector = [vertex2[0] - vertex1[0], vertex2[1] - vertex1[1]];
+ let midpoint = [(vertex1[0] + vertex2[0]) / 2, (vertex1[1] + vertex2[1]) / 2];
+ let sideLength = Math.sqrt(sideVector[0] ** 2 + sideVector[1] ** 2);
+ let inradius = sideLength / (2 * Math.tan(Math.PI / nVertices));
+
+ let inradiusDirection = [-sideVector[1] / sideLength, sideVector[0] / sideLength];
+
+ let center = [midpoint[0] + inradiusDirection[0] * inradius, midpoint[1] + inradiusDirection[1] * inradius];
+
+
+ runTests({
+ nVertices,
+ vertex1,
+ center,
+ conservedWhenChangeNvertices: "twoVertices",
+ abbreviated: true,
+ });
+
+ });
+
+ it('specify circumradius for triangle, ignore all other size attributes', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ circumradius: "11",
+ inradius: "3",
+ sideLength: "5",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [11, 0],
+ center: [0, 0],
+ conservedWhenChangeNvertices: "circumradius",
+ abbreviated: true,
+ });
+
+
+ });
+
+ it('specify radius for triangle, ignore all other size attributes', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ radius: "11",
+ inradius: "3",
+ sideLength: "5",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [11, 0],
+ center: [0, 0],
+ conservedWhenChangeNvertices: "circumradius",
+ abbreviated: true,
+ });
+
+
+ });
+
+ it('specify inradius for triangle, ignore all other size attributes', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ inradius: "3",
+ sideLength: "5",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [3 / (Math.cos(Math.PI / 3)), 0],
+ center: [0, 0],
+ conservedWhenChangeNvertices: "inradius",
+ abbreviated: true,
+ });
+
+
+ });
+
+ it('specify center and apothem for triangle, ignore all other size attributes', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ center: "(-1,-3)",
+ apothem: "3",
+ sideLength: "5",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [-1 + 3 / (Math.cos(Math.PI / 3)), -3],
+ center: [-1, -3],
+ conservedWhenChangeNvertices: "inradius",
+ abbreviated: true,
+ });
+
+
+ });
+
+ it('specify sideLength for triangle, ignore all other size attributes', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ sideLength: "5",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [5 / (2 * Math.sin(Math.PI / 3)), 0],
+ center: [0, 0],
+ conservedWhenChangeNvertices: "sideLength",
+ abbreviated: true,
+ });
+
+
+ });
+
+ it('specify center and perimeter for triangle, ignore area', () => {
+
+ setupScene({
+ attributes: {
+ nVertices: "3",
+ center: "(-1,-3)",
+ perimeter: "10",
+ area: "99",
+ },
+ });
+
+ runTests({
+ nVertices: 3,
+ vertex1: [-1 + 10 / (3 * 2 * Math.sin(Math.PI / 3)), -3],
+ center: [-1, -3],
+ conservedWhenChangeNvertices: "perimeter",
+ abbreviated: true,
+ });
+
+
+ });
+
+ it('draggable, vertices draggable', () => {
+ cy.window().then(async (win) => {
+ win.postMessage({
+ doenetML: `
+
+
+
+ To wait:
+ draggable:
+ vertices draggable:
+ two vertices: $p.vertex1 $p.vertex2
+ `}, "*");
+ });
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'false')
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(1,3)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,7)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+ cy.log('cannot move single vertex')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: { 0: [4, 7] }
+ }
+ })
+ })
+
+
+ // wait for core to process click
+ cy.get('#\\/bi').click()
+ cy.get('#\\/bi2').should('have.text', 'true')
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'false')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(1,3)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,7)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+
+
+ cy.log('cannot move all vertices')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: [[4, 7], [8, 10], [1, 9]]
+ }
+ })
+ })
+
+
+ // wait for core to process click
+ cy.get('#\\/bi').click()
+ cy.get('#\\/bi2').should('have.text', 'false')
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'false')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(1,3)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,7)')
+
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+
+ cy.log('only vertices draggable')
+
+ cy.get('#\\/verticesDraggable').click()
+ cy.get('#\\/vd2').should('have.text', 'true')
+
+
+ cy.log('can move single vertex')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: { 0: [4, 7] }
+ }
+ })
+ })
+
+
+ cy.get("#\\/pvert .mjx-mrow").should('contain.text', '(4,7)')
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'true')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(4,7)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(true);
+ })
+
+
+
+ cy.log('cannot move all vertices')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: [[3, 8], [8, 10], [1, 9]]
+ }
+ })
+ })
+
+
+ // wait for core to process click
+ cy.get('#\\/bi').click()
+ cy.get('#\\/bi2').should('have.text', 'true')
+
+ cy.get("#\\/d2").should('have.text', 'false')
+ cy.get("#\\/vd2").should('have.text', 'true')
+
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(4,7)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect((stateVariables['/p'].stateValues.vertices)[0]).eqls([4, 7]);
+ expect(stateVariables['/p'].stateValues.draggable).eq(false);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(true);
+ })
+
+
+
+ cy.log('vertices and polygon draggable')
+
+ cy.get('#\\/draggable').click()
+ cy.get('#\\/d2').should('have.text', 'true')
+
+
+ cy.log('can move single vertex')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: { 1: [-3, 2] }
+ }
+ })
+ })
+
+
+ cy.get("#\\/pvert .mjx-mrow").should('contain.text', '(−3,2)')
+
+ cy.get("#\\/d2").should('have.text', 'true')
+ cy.get("#\\/vd2").should('have.text', 'true')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(−3,2)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect(stateVariables['/p'].stateValues.draggable).eq(true);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(true);
+ })
+
+
+
+ cy.log('can move all vertices')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: [[3, 8], [5, 8], [4, 8 + Math.sqrt(3)]]
+ }
+ })
+ })
+
+
+ cy.get("#\\/pvert .mjx-mrow").should('contain.text', '(3,8)')
+
+
+ cy.get("#\\/d2").should('have.text', 'true')
+ cy.get("#\\/vd2").should('have.text', 'true')
+
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(3,8)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,8)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect(stateVariables['/p'].stateValues.draggable).eq(true);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(true);
+ })
+
+
+ cy.log('polygon but not vertices draggable')
+
+ cy.get('#\\/verticesDraggable').click()
+ cy.get('#\\/vd2').should('have.text', 'false')
+
+
+ cy.log('cannot move single vertex')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: { 0: [9, 3] }
+ }
+ })
+ })
+
+ // wait for core to process click
+ cy.get('#\\/bi').click()
+ cy.get('#\\/bi2').should('have.text', 'false')
+
+
+ cy.get("#\\/d2").should('have.text', 'true')
+ cy.get("#\\/vd2").should('have.text', 'false')
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(3,8)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(5,8)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect(stateVariables['/p'].stateValues.draggable).eq(true);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+
+
+ cy.log('can move all vertices')
+ cy.window().then(async (win) => {
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: "/p",
+ args: {
+ pointCoords: [[-4, 1], [-4, 5], [-4 - 2 * Math.sqrt(3), 3]]
+ }
+ })
+ })
+
+
+ cy.get("#\\/pvert .mjx-mrow").should('contain.text', '(−4,1)')
+
+
+ cy.get("#\\/d2").should('have.text', 'true')
+ cy.get("#\\/vd2").should('have.text', 'false')
+
+
+ cy.get("#\\/pvert .mjx-mrow").eq(0).should('have.text', '(−4,1)')
+ cy.get("#\\/pvert .mjx-mrow").eq(2).should('have.text', '(−4,5)')
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+ expect(stateVariables['/p'].stateValues.draggable).eq(true);
+ expect(stateVariables['/p'].stateValues.verticesDraggable).eq(false);
+ })
+
+
+
+ })
+
+
+});
+
+function setupScene({ attributes }) {
+
+ let attributesString = Object.keys(attributes).map(attr => `${attr} = "${attributes[attr]}"`).join(" ");
+
+ cy.window().then(async (win) => {
+ win.postMessage({
+ doenetML: `
+ a
+
+
+
+
+
+
+
+
+
+
+
+ circumradius:
+ radius:
+
+ inradius:
+ apothem:
+
+ side length:
+ perimeter:
+
+
+ area:
+
+ n vertices:
+
+
+
+
+
+
+
+ `}, "*");
+ });
+}
+
+function runTests({ center, vertex1, nVertices, conservedWhenChangeNvertices = "radius", abbreviated = false }) {
+ cy.get('#\\/_text1').should('have.text', 'a'); // to wait for page to load
+
+ cy.window().then(async (win) => {
+ let stateVariables = await win.returnAllStateVariables1();
+
+ let polygonName = "/rp";
+ let centerPointName = "/centerPoint";
+ let allVertexNames = ["/v1", "/v2", "/v3", "/v4", "/v5", "/v6", "/v7", "/v8", "/v9", "/v10"];
+ let polygonCopyName = "/rp2";
+ let polygonCopy2Name = "/g4/rp2";
+
+ let inputs = {
+ polygonNames: [polygonName, polygonCopyName, polygonCopy2Name],
+ vertexNames: allVertexNames.slice(0, nVertices),
+ centerPointName
+ }
+
+ cy.window().then(async (win) => {
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+
+
+ cy.log("move vertices individually");
+
+ for (let i = 0; i < 3; i++) {
+ let index = (i * Math.round(nVertices / 3)) % nVertices;
+
+ cy.window().then(async (win) => {
+
+ let vertex = [index, index + 1];
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: polygonName,
+ args: {
+ pointCoords: { [index]: vertex }
+ }
+ })
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let angle = -index * 2 * Math.PI / nVertices;
+ let c = Math.cos(angle);
+ let s = Math.sin(angle);
+
+
+ let directionWithRadius = [vertex[0] - center[0], vertex[1] - center[1]];
+
+ vertex1 = [directionWithRadius[0] * c - directionWithRadius[1] * s + center[0], directionWithRadius[0] * s + directionWithRadius[1] * c + center[1]]
+
+
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+
+ }
+
+
+ if (!abbreviated) {
+ cy.log("move polygon points together");
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let dx = 3, dy = -2;
+
+ let currentVertices = stateVariables[polygonName].stateValues.vertices;
+ let pointCoords = {};
+
+ for (let i = 0; i < nVertices; i++) {
+ pointCoords[i] = [currentVertices[i][0] + dx, currentVertices[i][1] + dy]
+ }
+
+ vertex1 = pointCoords[0];
+ center = [center[0] + dx, center[1] + dy]
+
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: polygonName,
+ args: {
+ pointCoords
+ }
+ })
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+
+ cy.log("move center point");
+ cy.window().then(async (win) => {
+
+ vertex1 = [vertex1[0] - center[0], vertex1[1] - center[1]]
+ center = [0, 0];
+
+ await win.callAction1({
+ actionName: "movePoint",
+ componentName: centerPointName,
+ args: { x: center[0], y: center[1] }
+ });
+
+
+
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+
+
+ cy.log("move copied vertices");
+ for (let i = 0; i < 3; i++) {
+ let index = (i * Math.round(nVertices / 3) + 1) % nVertices;
+ cy.window().then(async (win) => {
+
+ let vertex = [index / 2 + 3, -1.5 * index];
+
+ await win.callAction1({
+ actionName: "movePoint",
+ componentName: allVertexNames[index],
+ args: { x: vertex[0], y: vertex[1] }
+ });
+
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let angle = -index * 2 * Math.PI / nVertices;
+ let c = Math.cos(angle);
+ let s = Math.sin(angle);
+
+
+ let directionWithRadius = [vertex[0] - center[0], vertex[1] - center[1]];
+
+ vertex1 = [directionWithRadius[0] * c - directionWithRadius[1] * s + center[0], directionWithRadius[0] * s + directionWithRadius[1] * c + center[1]]
+
+
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+
+ }
+
+
+ cy.log("move polygonCopy vertices individually");
+ for (let i = 0; i < 3; i++) {
+ let index = (i * Math.round(nVertices / 3) + 2) % nVertices;
+
+ cy.window().then(async (win) => {
+
+ let vertex = [-index - 1, 2 * index];
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: polygonCopyName,
+ args: {
+ pointCoords: { [index]: vertex }
+ }
+ })
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let angle = -index * 2 * Math.PI / nVertices;
+ let c = Math.cos(angle);
+ let s = Math.sin(angle);
+
+
+ let directionWithRadius = [vertex[0] - center[0], vertex[1] - center[1]];
+
+ vertex1 = [directionWithRadius[0] * c - directionWithRadius[1] * s + center[0], directionWithRadius[0] * s + directionWithRadius[1] * c + center[1]]
+
+
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+
+ }
+ }
+
+ cy.log("polygonCopy vertices together");
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let dx = -2, dy = -4;
+
+ let currentVertices = stateVariables[polygonCopyName].stateValues.vertices;
+ let pointCoords = {};
+
+ for (let i = 0; i < nVertices; i++) {
+ pointCoords[i] = [currentVertices[i][0] + dx, currentVertices[i][1] + dy]
+ }
+
+ vertex1 = pointCoords[0];
+ center = [center[0] + dx, center[1] + dy]
+
+
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: polygonCopyName,
+ args: {
+ pointCoords
+ }
+ })
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+
+
+ if (!abbreviated) {
+
+ cy.log("move polygonCopy2 vertices individually");
+ for (let i = 0; i < 3; i++) {
+ let index = (i * Math.round(nVertices / 3) + 3) % nVertices;
+ cy.window().then(async (win) => {
+
+ let vertex = [-2 * index - 1, index + 4];
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: polygonCopy2Name,
+ args: {
+ pointCoords: { [index]: vertex }
+ }
+ })
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let angle = -index * 2 * Math.PI / nVertices;
+ let c = Math.cos(angle);
+ let s = Math.sin(angle);
+
+
+ let directionWithRadius = [vertex[0] - center[0], vertex[1] - center[1]];
+
+ vertex1 = [directionWithRadius[0] * c - directionWithRadius[1] * s + center[0], directionWithRadius[0] * s + directionWithRadius[1] * c + center[1]]
+
+
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+
+ }
+
+
+ cy.log("polygonCopy2 vertices together");
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let dx = 1, dy = -3;
+
+ let currentVertices = stateVariables[polygonCopyName].stateValues.vertices;
+ let pointCoords = {};
+
+ for (let i = 0; i < nVertices; i++) {
+ pointCoords[i] = [currentVertices[i][0] + dx, currentVertices[i][1] + dy]
+ }
+
+ vertex1 = pointCoords[0];
+ center = [center[0] + dx, center[1] + dy]
+
+
+
+ await win.callAction1({
+ actionName: "movePolygon",
+ componentName: polygonCopy2Name,
+ args: {
+ pointCoords
+ }
+ })
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ }
+
+
+ cy.log("Change circumradius")
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let oldCr = stateVariables[polygonName].stateValues.circumradius;
+
+ let circumradius = 1;
+
+ cy.get("#\\/micr textarea").type(`{home}{shift+end}{backspace}${circumradius}{enter}`, { force: true })
+ cy.get("#\\/cr").should('have.text', `${circumradius}`)
+
+ vertex1 = [
+ (vertex1[0] - center[0]) * circumradius / oldCr + center[0],
+ (vertex1[1] - center[1]) * circumradius / oldCr + center[1]
+ ]
+
+ cy.window().then(async (win) => {
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ })
+
+
+ if (!abbreviated) {
+
+ cy.log("Change radius")
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let oldR = stateVariables[polygonName].stateValues.circumradius;
+
+ let radius = 3;
+
+ cy.get("#\\/mir textarea").type(`{home}{shift+end}{backspace}${radius}{enter}`, { force: true })
+ cy.get("#\\/r").should('have.text', `${radius}`)
+
+ vertex1 = [
+ (vertex1[0] - center[0]) * radius / oldR + center[0],
+ (vertex1[1] - center[1]) * radius / oldR + center[1]
+ ]
+
+ cy.window().then(async (win) => {
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ })
+
+
+ cy.log("Change inradius")
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let oldIr = stateVariables[polygonName].stateValues.inradius;
+
+ let inradius = 5;
+
+ cy.get("#\\/miir textarea").type(`{home}{shift+end}{backspace}${inradius}{enter}`, { force: true })
+ cy.get("#\\/ir").should('have.text', `${inradius}`)
+
+ vertex1 = [
+ (vertex1[0] - center[0]) * inradius / oldIr + center[0],
+ (vertex1[1] - center[1]) * inradius / oldIr + center[1]
+ ]
+
+ cy.window().then(async (win) => {
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ })
+
+
+ cy.log("Change apothem")
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let oldAp = stateVariables[polygonName].stateValues.inradius;
+
+ let apothem = 4;
+
+ cy.get("#\\/miap textarea").type(`{home}{shift+end}{backspace}${apothem}{enter}`, { force: true })
+ cy.get("#\\/ap").should('have.text', `${apothem}`)
+
+ vertex1 = [
+ (vertex1[0] - center[0]) * apothem / oldAp + center[0],
+ (vertex1[1] - center[1]) * apothem / oldAp + center[1]
+ ]
+
+ cy.window().then(async (win) => {
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ })
+
+
+ cy.log("Change sideLength")
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let oldSl = stateVariables[polygonName].stateValues.sideLength;
+
+ let sideLength = 2;
+
+ cy.get("#\\/misl textarea").type(`{home}{shift+end}{backspace}${sideLength}{enter}`, { force: true })
+ cy.get("#\\/sl").should('have.text', `${sideLength}`)
+
+ vertex1 = [
+ (vertex1[0] - center[0]) * sideLength / oldSl + center[0],
+ (vertex1[1] - center[1]) * sideLength / oldSl + center[1]
+ ]
+
+ cy.window().then(async (win) => {
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ })
+
+
+ cy.log("Change perimeter")
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let oldSl = stateVariables[polygonName].stateValues.perimeter;
+
+ let perimeter = 9;
+
+ cy.get("#\\/mip textarea").type(`{home}{shift+end}{backspace}${perimeter}{enter}`, { force: true })
+ cy.get("#\\/p").should('have.text', `${perimeter}`)
+
+ vertex1 = [
+ (vertex1[0] - center[0]) * perimeter / oldSl + center[0],
+ (vertex1[1] - center[1]) * perimeter / oldSl + center[1]
+ ]
+
+ cy.window().then(async (win) => {
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ })
+
+
+ cy.log("Change area")
+ cy.window().then(async (win) => {
+
+ stateVariables = await win.returnAllStateVariables1();
+
+ let oldAr = stateVariables[polygonName].stateValues.area;
+
+ let area = 13;
+
+ cy.get("#\\/miar textarea").type(`{home}{shift+end}{backspace}${area}{enter}`, { force: true })
+ cy.get("#\\/ar").should('have.text', `${area}`)
+
+ vertex1 = [
+ (vertex1[0] - center[0]) * Math.sqrt(area / oldAr) + center[0],
+ (vertex1[1] - center[1]) * Math.sqrt(area / oldAr) + center[1]
+ ]
+
+ cy.window().then(async (win) => {
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ })
+
+
+ }
+
+
+
+ cy.log("Add two vertices")
+ cy.window().then(async (win) => {
+
+
+ let result = adjustVertex1CenterWhenChangeNVertices(vertex1, center, nVertices, nVertices + 2, conservedWhenChangeNvertices);
+
+ vertex1 = result.vertex1;
+ center = result.center;
+
+ nVertices += 2;
+
+ cy.get("#\\/minv textarea").type(`{end}+2{enter}`, { force: true })
+ cy.get("#\\/nv").should('have.text', `${nVertices}`)
+
+
+ inputs.vertexNames = allVertexNames.slice(0, nVertices);
+
+
+ cy.window().then(async (win) => {
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ })
+
+
+
+ if (!abbreviated) {
+
+ cy.log("Remove a vertex")
+ cy.window().then(async (win) => {
+
+ let result = adjustVertex1CenterWhenChangeNVertices(vertex1, center, nVertices, nVertices - 1, conservedWhenChangeNvertices);
+
+ vertex1 = result.vertex1;
+ center = result.center;
+
+
+ nVertices -= 1;
+
+ cy.get("#\\/minv textarea").type(`{end}-1{enter}`, { force: true })
+ cy.get("#\\/nv").should('have.text', `${nVertices}`)
+
+
+ inputs.vertexNames = allVertexNames.slice(0, nVertices);
+
+
+ cy.window().then(async (win) => {
+ stateVariables = await win.returnAllStateVariables1();
+ checkPolygonValues(
+ inputs,
+ {
+ nVertices,
+ vertex1,
+ center,
+ },
+ stateVariables
+ );
+ })
+ })
+ }
+
+
+
+ })
+}
+
+function adjustVertex1CenterWhenChangeNVertices(vertex1, center, nVerticesOld, nVerticesNew, conservedWhenChangeNvertices) {
+
+ let radiusRatio = 1;
+
+ if (conservedWhenChangeNvertices === "inradius") {
+
+ radiusRatio = Math.cos(Math.PI / nVerticesOld) / Math.cos(Math.PI / nVerticesNew)
+
+ } else if (conservedWhenChangeNvertices === "sideLength") {
+
+ radiusRatio = (2 * Math.sin(Math.PI / nVerticesOld)) / (2 * Math.sin(Math.PI / nVerticesNew))
+
+ } else if (conservedWhenChangeNvertices === "perimeter") {
+
+ radiusRatio = (2 * nVerticesOld * Math.sin(Math.PI / nVerticesOld)) / (2 * nVerticesNew * Math.sin(Math.PI / nVerticesNew))
+
+ } else if (conservedWhenChangeNvertices === "area") {
+ radiusRatio = Math.sqrt((nVerticesOld / 2 * Math.sin(2 * Math.PI / nVerticesOld))
+ / (nVerticesNew / 2 * Math.sin(2 * Math.PI / nVerticesNew)))
+
+ } else if (conservedWhenChangeNvertices === "twoVertices") {
+
+
+ // calculate vertex2
+ let directionWithRadius = [vertex1[0] - center[0], vertex1[1] - center[1]];
+
+ let angleOld = 2 * Math.PI / nVerticesOld;
+ let c = Math.cos(angleOld);
+ let s = Math.sin(angleOld);
+
+ let vertex2 = [directionWithRadius[0] * c - directionWithRadius[1] * s + center[0], directionWithRadius[0] * s + directionWithRadius[1] * c + center[1]];
+
+ // calculate center based on this vertex 2 and new nVertices
+ let sideVector = [vertex2[0] - vertex1[0], vertex2[1] - vertex1[1]];
+ let midpoint = [(vertex1[0] + vertex2[0]) / 2, (vertex1[1] + vertex2[1]) / 2];
+ let sideLength = Math.sqrt(sideVector[0] ** 2 + sideVector[1] ** 2);
+ let inradius = sideLength / (2 * Math.tan(Math.PI / nVerticesNew));
+
+ let inradiusDirection = [-sideVector[1] / sideLength, sideVector[0] / sideLength];
+
+ center = [midpoint[0] + inradiusDirection[0] * inradius, midpoint[1] + inradiusDirection[1] * inradius];
+
+ }
+
+
+ vertex1 = [
+ (vertex1[0] - center[0]) * radiusRatio + center[0],
+ (vertex1[1] - center[1]) * radiusRatio + center[1]
+ ]
+
+ return { vertex1, center };
+
+}
+
+
+function checkPolygonValues({
+ polygonNames,
+ vertexNames,
+ centerPointName
+}, {
+ nVertices,
+ center,
+ vertex1,
+},
+ stateVariables
+) {
+
+
+ let vertexCoords = [vertex1];
+
+ let directionWithRadius = [vertex1[0] - center[0], vertex1[1] - center[1]];
+
+ let circumradius = Math.sqrt(directionWithRadius[0] ** 2 + directionWithRadius[1] ** 2);
+ let inradius = circumradius * Math.cos(Math.PI / nVertices);
+ let sideLength = circumradius * (2 * Math.sin(Math.PI / nVertices));
+ let perimeter = circumradius * (2 * nVertices * Math.sin(Math.PI / nVertices));
+ let area = circumradius ** 2 * (nVertices / 2 * Math.sin(2 * Math.PI / nVertices))
+
+
+
+ for (let i = 1; i < nVertices; i++) {
+ let angle = i * 2 * Math.PI / nVertices;
+ let c = Math.cos(angle);
+ let s = Math.sin(angle);
+
+ vertexCoords.push([directionWithRadius[0] * c - directionWithRadius[1] * s + center[0], directionWithRadius[0] * s + directionWithRadius[1] * c + center[1]])
+
+ }
+
+ for (let polygonName of polygonNames) {
+ let polygon = stateVariables[polygonName];
+ for (let i = 0; i < nVertices; i++) {
+ expect(polygon.stateValues.vertices[i][0]).closeTo(vertexCoords[i][0], 1E-14 * Math.abs(vertexCoords[i][0]) + 1E-14)
+ expect(polygon.stateValues.vertices[i][1]).closeTo(vertexCoords[i][1], 1E-14 * Math.abs(vertexCoords[i][1]) + 1E-14)
+ }
+ expect(polygon.stateValues.center[0]).closeTo(center[0], 1E-14 * Math.abs(center[0]) + 1E-14);
+ expect(polygon.stateValues.center[1]).closeTo(center[1], 1E-14 * Math.abs(center[1]) + 1E-14);
+
+ expect((polygon.stateValues.nVertices)).eq(nVertices);
+
+ expect((polygon.stateValues.circumradius)).closeTo(circumradius, 1E-14 * circumradius);
+ expect((polygon.stateValues.inradius)).closeTo(inradius, 1E-14 * inradius);
+ expect((polygon.stateValues.sideLength)).closeTo(sideLength, 1E-14 * sideLength);
+ expect((polygon.stateValues.perimeter)).closeTo(perimeter, 1E-14 * perimeter);
+ expect((polygon.stateValues.area)).closeTo(area, 1E-14 * area);
+
+ }
+
+ if (vertexNames) {
+ for (let [i, vertexName] of vertexNames.entries()) {
+ let vertex = stateVariables[vertexName];
+ expect(vertex.stateValues.xs[0]).closeTo(vertexCoords[i][0], 1E-14 * Math.abs(vertexCoords[i][0]) + 1E-14)
+ expect(vertex.stateValues.xs[1]).closeTo(vertexCoords[i][1], 1E-14 * Math.abs(vertexCoords[i][1]) + 1E-14)
+ }
+ }
+
+ if (centerPointName) {
+ let centerPoint = stateVariables[centerPointName];
+ expect(centerPoint.stateValues.xs[0]).closeTo(center[0], 1E-14 * Math.abs(center[0]) + 1E-14);
+ expect(centerPoint.stateValues.xs[1]).closeTo(center[1], 1E-14 * Math.abs(center[1]) + 1E-14);
+
+ }
+}
\ No newline at end of file
diff --git a/src/Core/ComponentTypes.js b/src/Core/ComponentTypes.js
index dcc3f5f404..4a02983eca 100644
--- a/src/Core/ComponentTypes.js
+++ b/src/Core/ComponentTypes.js
@@ -48,6 +48,7 @@ import Polyline from './components/Polyline';
import Polygon from './components/Polygon';
import Triangle from './components/Triangle';
import Rectangle from './components/Rectangle';
+import RegularPolygon from './components/RegularPolygon';
import Circle from './components/Circle';
import Parabola from './components/Parabola';
import Curve from './components/Curve';
@@ -228,6 +229,7 @@ const componentTypeArray = [
Polygon,
Triangle,
Rectangle,
+ RegularPolygon,
Circle,
Parabola,
Curve,
diff --git a/src/Core/Core.js b/src/Core/Core.js
index 3f11a729f2..cc93678668 100644
--- a/src/Core/Core.js
+++ b/src/Core/Core.js
@@ -10449,7 +10449,7 @@ export default class Core {
} else {
startInd = range.openBegin !== undefined ? range.openBegin : range.selfCloseBegin;
- endInd = range.closeEnd !== undefined ? range.closeEnd : range.selfCloseEnd + 1;
+ endInd = range.closeEnd !== undefined ? range.closeEnd : range.selfCloseEnd;
}
let componentDoenetML = this.doenetML.slice(startInd - 1, endInd);
diff --git a/src/Core/components/Rectangle.js b/src/Core/components/Rectangle.js
index 359e431a11..40e1c126c6 100644
--- a/src/Core/components/Rectangle.js
+++ b/src/Core/components/Rectangle.js
@@ -898,12 +898,6 @@ export default class Rectangle extends Polygon {
// console.log("inverse definition of vertices of rectangle",
// desiredStateVariableValues, dependencyValuesByKey, stateValues);
- // if not draggable, then disallow initial change
-
- if (initialChange && !await stateValues.draggable) {
- return { success: false };
- }
-
if (!workspace.v0) {
let vertices = await stateValues.vertices;
workspace.v0 = [...vertices[0]];
@@ -1155,6 +1149,21 @@ export default class Rectangle extends Polygon {
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 });
+ }
+ } else {
+ // whole rectangle dragged
+ if (!await this.stateValues.draggable) {
+ return await this.coreFunctions.resolveAction({ actionId });
+ }
+ }
+
let updateInstructions = [];
let vertexComponents = {};
diff --git a/src/Core/components/RegularPolygon.js b/src/Core/components/RegularPolygon.js
new file mode 100644
index 0000000000..8197324e02
--- /dev/null
+++ b/src/Core/components/RegularPolygon.js
@@ -0,0 +1,1524 @@
+import Polygon from './Polygon';
+import me from "math-expressions";
+
+export default class RegularPolygon extends Polygon {
+ static componentType = "regularPolygon";
+ static rendererType = "polygon";
+
+
+ static createAttributesObject() {
+ let attributes = super.createAttributesObject();
+
+ attributes.nVertices = {
+ createComponentOfType: "number",
+ createStateVariable: "nVertices",
+ defaultValue: 3,
+ public: true,
+ forRenderer: true,
+ };
+
+ // Note: vertices is already an attribute from polygon
+
+ attributes.center = {
+ createComponentOfType: "point"
+ }
+
+
+ // if center and vertex or two vertices are specified
+ // then the following size attributes are ignored
+
+ // circumradius and radrius are the same thing and either attribute can be used
+ // If both specified, circumradius is used
+ attributes.circumradius = {
+ createComponentOfType: "number"
+ }
+ attributes.radius = {
+ createComponentOfType: "number"
+ }
+
+
+ // inradius and apothem are the same thing and either attribute can be used
+ // If both specified, inradius is used.
+ // If circumradius is specified, inradius is ignored
+ attributes.inradius = {
+ createComponentOfType: "number"
+ }
+ attributes.apothem = {
+ createComponentOfType: "number"
+ }
+
+ // if circumradius or inradius is specified, sideLength is ignored
+ attributes.sideLength = {
+ createComponentOfType: "number"
+ }
+
+ // if circumradius, inradius, or sideLength is specified, perimeter is ignored
+ attributes.perimeter = {
+ createComponentOfType: "number"
+ }
+
+ // if circumradius, inradius, sideLength, or perimeter is specified, area is ignored
+ attributes.area = {
+ createComponentOfType: "number"
+ }
+
+ return attributes;
+ }
+
+ static returnStateVariableDefinitions() {
+
+ let stateVariableDefinitions = super.returnStateVariableDefinitions();
+
+ delete stateVariableDefinitions.nVertices;
+
+ let styleDescriptionWithNounDeps = stateVariableDefinitions.styleDescriptionWithNoun.returnDependencies();
+ styleDescriptionWithNounDeps.nSides = {
+ dependencyType: "stateVariable",
+ variableName: "nSides"
+ }
+
+ stateVariableDefinitions.styleDescriptionWithNoun.returnDependencies = () => styleDescriptionWithNounDeps;
+
+
+ let styleDescriptionWithNounDef = stateVariableDefinitions.styleDescriptionWithNoun.definition;
+
+ stateVariableDefinitions.styleDescriptionWithNoun.definition = function ({ dependencyValues }) {
+ let styleDescriptionWithNoun = styleDescriptionWithNounDef({ dependencyValues }).setValue.styleDescriptionWithNoun;
+
+ styleDescriptionWithNoun = styleDescriptionWithNoun.replaceAll("polygon", `${dependencyValues.nSides}-sided regular polygon`);
+
+ return { setValue: { styleDescriptionWithNoun } }
+ }
+
+ stateVariableDefinitions.nVerticesSpecified = {
+
+ returnDependencies: () => ({
+ verticesAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "vertices",
+ variableNames: ["nPoints"]
+ }
+ }),
+ definition: function ({ dependencyValues }) {
+ if (dependencyValues.verticesAttr !== null) {
+ return { setValue: { nVerticesSpecified: dependencyValues.verticesAttr.stateValues.nPoints } }
+ } else {
+ return { setValue: { nVerticesSpecified: 0 } }
+ }
+
+ }
+ }
+
+ stateVariableDefinitions.essentialDirection = {
+ isArray: true,
+ entryPrefixes: ["essentialVertexX"],
+ defaultValueByArrayKey: () => 0,
+ hasEssential: true,
+ returnArraySizeDependencies: () => ({
+ nVerticesSpecified: {
+ dependencyType: "stateVariable",
+ variableName: "nVerticesSpecified",
+ },
+ haveSpecifiedCenter: {
+ dependencyType: "stateVariable",
+ variableName: "haveSpecifiedCenter",
+ }
+ }),
+ returnArraySize({ dependencyValues }) {
+ let needDir = (dependencyValues.haveSpecifiedCenter ? 1 : 0) + dependencyValues.nVerticesSpecified <= 1;
+ return [needDir ? 2 : 0];
+ },
+
+ returnArrayDependenciesByKey() {
+ return {};
+ },
+
+ arrayDefinitionByKey: function ({ arrayKeys }) {
+ let essentialDirection = {};
+
+ for (let arrayKey of arrayKeys) {
+ if (arrayKey === "0") {
+ essentialDirection[arrayKey] = { defaultValue: 1 }
+ } else {
+ // uses defaultValueByArrayKey
+ essentialDirection[arrayKey] = true;
+ }
+ }
+ return { useEssentialOrDefaultValue: { essentialDirection } };
+
+ },
+
+ inverseArrayDefinitionByKey({ desiredStateVariableValues }) {
+
+ let instructions = [];
+
+ for (let arrayKey in desiredStateVariableValues.essentialDirection) {
+
+ instructions.push({
+ setEssentialValue: "essentialDirection",
+ value: { [arrayKey]: desiredStateVariableValues.essentialDirection[arrayKey] },
+ });
+ }
+
+ return {
+ success: true,
+ instructions
+ }
+ }
+ }
+
+ stateVariableDefinitions.haveSpecifiedCenter = {
+ returnDependencies: () => ({
+ centerAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "center"
+ }
+ }),
+ definition: ({ dependencyValues }) => ({
+ setValue: {
+ haveSpecifiedCenter: dependencyValues.centerAttr !== null
+ }
+ })
+ }
+
+ stateVariableDefinitions.specifiedCenter = {
+ isArray: true,
+ entryPrefixes: ["specifiedCenterX"],
+ returnArraySizeDependencies: () => ({
+ haveSpecifiedCenter: {
+ dependencyType: "stateVariable",
+ variableName: "haveSpecifiedCenter",
+ },
+ }),
+ returnArraySize({ dependencyValues }) {
+ return [dependencyValues.haveSpecifiedCenter ? 2 : 0];
+ },
+
+ returnArrayDependenciesByKey({ arrayKeys }) {
+
+ let dependenciesByKey = {};
+
+ for (let arrayKey of arrayKeys) {
+ let varEnding = Number(arrayKey) + 1;
+ dependenciesByKey[arrayKey] = {
+ centerAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "center",
+ variableNames: ["x" + varEnding],
+ },
+ }
+ }
+
+ return { dependenciesByKey }
+ },
+
+ arrayDefinitionByKey: function ({ dependencyValuesByKey, arrayKeys }) {
+
+ let specifiedCenter = {};
+
+ for (let arrayKey of arrayKeys) {
+ let varEnding = Number(arrayKey) + 1;
+
+ if (dependencyValuesByKey[arrayKey].centerAttr !== null) {
+ specifiedCenter[arrayKey] = evaluate_to_constant(dependencyValuesByKey[arrayKey].centerAttr.stateValues["x" + varEnding]);
+ }
+ }
+
+ return { setValue: { specifiedCenter } }
+ },
+
+ inverseArrayDefinitionByKey({ desiredStateVariableValues,
+ dependencyValuesByKey, dependencyNamesByKey,
+ }) {
+ let instructions = [];
+
+ for (let arrayKey in desiredStateVariableValues.specifiedCenter) {
+
+ if (dependencyValuesByKey[arrayKey].centerAttr &&
+ dependencyValuesByKey[arrayKey].centerAttr !== null
+ ) {
+
+ instructions.push({
+ setDependency: dependencyNamesByKey[arrayKey].centerAttr,
+ desiredValue: me.fromAst(desiredStateVariableValues.specifiedCenter[arrayKey]),
+ variableIndex: 0,
+ })
+ }
+ }
+
+ return {
+ success: true,
+ instructions
+ };
+
+ }
+ }
+
+
+ stateVariableDefinitions.essentialCenter = {
+ isArray: true,
+ entryPrefixes: ["essentialCenterX"],
+ defaultValueByArrayKey: () => 0,
+ hasEssential: true,
+ returnArraySizeDependencies: () => ({
+ haveSpecifiedCenter: {
+ dependencyType: "stateVariable",
+ variableName: "haveSpecifiedCenter",
+ }
+ }),
+ returnArraySize({ dependencyValues }) {
+ return [dependencyValues.haveSpecifiedCenter ? 0 : 2];
+ },
+
+ returnArrayDependenciesByKey() {
+ return {};
+ },
+
+ arrayDefinitionByKey: function ({ arrayKeys }) {
+ let essentialCenter = {};
+
+ for (let arrayKey of arrayKeys) {
+ essentialCenter[arrayKey] = true;
+ }
+ return { useEssentialOrDefaultValue: { essentialCenter } };
+
+ },
+
+ inverseArrayDefinitionByKey({ desiredStateVariableValues }) {
+
+ let instructions = [];
+
+ for (let arrayKey in desiredStateVariableValues.essentialCenter) {
+
+ instructions.push({
+ setEssentialValue: "essentialCenter",
+ value: { [arrayKey]: desiredStateVariableValues.essentialCenter[arrayKey] },
+ });
+ }
+
+ return {
+ success: true,
+ instructions
+ }
+ }
+ }
+
+
+
+ stateVariableDefinitions.specifiedCircumradius = {
+ returnDependencies() {
+ return {
+ circumradiusAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "circumradius",
+ variableNames: ["value"]
+ },
+ radiusAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "radius",
+ variableNames: ["value"]
+ },
+ nVerticesSpecified: {
+ dependencyType: "stateVariable",
+ variableName: "nVerticesSpecified"
+ },
+ haveSpecifiedCenter: {
+ dependencyType: "stateVariable",
+ variableName: "haveSpecifiedCenter"
+ }
+ }
+ },
+
+ definition({ dependencyValues }) {
+ if (dependencyValues.circumradiusAttr !== null) {
+ return { setValue: { specifiedCircumradius: dependencyValues.circumradiusAttr.stateValues.value } };
+
+ } else if (dependencyValues.radiusAttr !== null) {
+ return { setValue: { specifiedCircumradius: dependencyValues.radiusAttr.stateValues.value } };
+
+ } else {
+ return { setValue: { specifiedCircumradius: null } }
+ }
+ },
+
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+ if (dependencyValues.circumradiusAttr !== null) {
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "circumradiusAttr",
+ desiredValue: desiredStateVariableValues.specifiedCircumradius,
+ childIndex: 0,
+ variableIndex: 0
+ }]
+ }
+ } else if (dependencyValues.radiusAttr !== null) {
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "radiusAttr",
+ desiredValue: desiredStateVariableValues.specifiedCircumradius,
+ childIndex: 0,
+ variableIndex: 0
+ }]
+ }
+ } else {
+ return { success: false }
+ }
+ }
+ }
+
+
+ stateVariableDefinitions.specifiedInradius = {
+ returnDependencies() {
+ return {
+ inradiusAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "inradius",
+ variableNames: ["value"]
+ },
+ apothemAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "apothem",
+ variableNames: ["value"]
+ },
+ }
+ },
+
+ definition({ dependencyValues }) {
+ if (dependencyValues.inradiusAttr !== null) {
+ return { setValue: { specifiedInradius: dependencyValues.inradiusAttr.stateValues.value } };
+
+ } else if (dependencyValues.apothemAttr !== null) {
+ return { setValue: { specifiedInradius: dependencyValues.apothemAttr.stateValues.value } };
+
+ } else {
+ return { setValue: { specifiedInradius: null } }
+ }
+ },
+
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+ if (dependencyValues.inradiusAttr !== null) {
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "inradiusAttr",
+ desiredValue: desiredStateVariableValues.specifiedInradius,
+ childIndex: 0,
+ variableIndex: 0
+ }]
+ }
+ } else if (dependencyValues.apothemAttr !== null) {
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "apothemAttr",
+ desiredValue: desiredStateVariableValues.specifiedInradius,
+ childIndex: 0,
+ variableIndex: 0
+ }]
+ }
+ } else {
+ return { sucess: false }
+ }
+ }
+ }
+
+
+ stateVariableDefinitions.specifiedSideLength = {
+ returnDependencies() {
+ return {
+ sideLengthAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "sideLength",
+ variableNames: ["value"]
+ },
+ }
+ },
+
+ definition({ dependencyValues }) {
+ if (dependencyValues.sideLengthAttr !== null) {
+ return { setValue: { specifiedSideLength: dependencyValues.sideLengthAttr.stateValues.value } };
+
+ } else {
+ return { setValue: { specifiedSideLength: null } }
+ }
+ },
+
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+ if (dependencyValues.sideLengthAttr !== null) {
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "sideLengthAttr",
+ desiredValue: desiredStateVariableValues.specifiedSideLength,
+ childIndex: 0,
+ variableIndex: 0
+ }]
+ }
+ } else {
+ return { sucess: false }
+ }
+ }
+ }
+
+
+ stateVariableDefinitions.specifiedPerimeter = {
+ returnDependencies() {
+ return {
+ perimeterAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "perimeter",
+ variableNames: ["value"]
+ },
+ }
+ },
+
+ definition({ dependencyValues }) {
+ if (dependencyValues.perimeterAttr !== null) {
+ return { setValue: { specifiedPerimeter: dependencyValues.perimeterAttr.stateValues.value } };
+
+ } else {
+ return { setValue: { specifiedPerimeter: null } }
+ }
+ },
+
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+ if (dependencyValues.perimeterAttr !== null) {
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "perimeterAttr",
+ desiredValue: desiredStateVariableValues.specifiedPerimeter,
+ childIndex: 0,
+ variableIndex: 0
+ }]
+ }
+ } else {
+ return { sucess: false }
+ }
+ }
+ }
+
+
+ stateVariableDefinitions.specifiedArea = {
+ returnDependencies() {
+ return {
+ areaAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "area",
+ variableNames: ["value"]
+ },
+ }
+ },
+
+ definition({ dependencyValues }) {
+ if (dependencyValues.areaAttr !== null) {
+ return { setValue: { specifiedArea: dependencyValues.areaAttr.stateValues.value } };
+
+ } else {
+ return { setValue: { specifiedArea: null } }
+ }
+ },
+
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+ if (dependencyValues.areaAttr !== null) {
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "areaAttr",
+ desiredValue: desiredStateVariableValues.specifiedArea,
+ childIndex: 0,
+ variableIndex: 0
+ }]
+ }
+ } else {
+ return { sucess: false }
+ }
+ }
+ }
+
+
+ stateVariableDefinitions.essentialCircumradius = {
+ hasEssential: true,
+ defaultValue: 1,
+ returnDependencies: () => ({}),
+ definition: () => ({ useEssentialOrDefaultValue: { essentialCircumradius: true } }),
+ inverseDefinition({ desiredStateVariableValues }) {
+ return {
+ success: true,
+ instructions: [{
+ setEssentialValue: "essentialCircumradius",
+ value: desiredStateVariableValues.essentialCircumradius
+ }]
+ }
+ }
+ }
+
+
+ // Note: we create the non-array centerComponents
+ // because we currently can't use additionalStateVariablesDefined with arrays
+ // unless all state variables are arrays of the same size
+ stateVariableDefinitions.centerComponents = {
+ additionalStateVariablesDefined: ["directionWithRadius"],
+ returnDependencies: () => ({
+ nVertices: {
+ dependencyType: "stateVariable",
+ variableName: "nVertices"
+ },
+ nVerticesSpecified: {
+ dependencyType: "stateVariable",
+ variableName: "nVerticesSpecified"
+ },
+ haveSpecifiedCenter: {
+ dependencyType: "stateVariable",
+ variableName: "haveSpecifiedCenter"
+ },
+
+ specifiedCircumradius: {
+ dependencyType: "stateVariable",
+ variableName: "specifiedCircumradius"
+ },
+ specifiedInradius: {
+ dependencyType: "stateVariable",
+ variableName: "specifiedInradius"
+ },
+ specifiedSideLength: {
+ dependencyType: "stateVariable",
+ variableName: "specifiedSideLength"
+ },
+ specifiedPerimeter: {
+ dependencyType: "stateVariable",
+ variableName: "specifiedPerimeter"
+ },
+ specifiedArea: {
+ dependencyType: "stateVariable",
+ variableName: "specifiedArea"
+ },
+
+ essentialCircumradius: {
+ dependencyType: "stateVariable",
+ variableName: "essentialCircumradius"
+ },
+ essentialDirection: {
+ dependencyType: "stateVariable",
+ variableName: "essentialDirection"
+ },
+
+ verticesAttr: {
+ dependencyType: "attributeComponent",
+ attributeName: "vertices",
+ variableNames: ["points"]
+ },
+
+ specifiedCenter: {
+ dependencyType: "stateVariable",
+ variableName: "specifiedCenter",
+ },
+
+ essentialCenter: {
+ dependencyType: "stateVariable",
+ variableName: "essentialCenter",
+ },
+ }),
+ definition({ dependencyValues }) {
+
+
+ let nVertices = dependencyValues.nVertices;
+
+ let center;
+ let directionWithRadius;
+
+ if (dependencyValues.nVerticesSpecified === 0) {
+
+ // with no vertices, use center (specified or essential), direction, and a measure of size
+
+ if (dependencyValues.haveSpecifiedCenter) {
+ center = dependencyValues.specifiedCenter;
+ } else {
+ center = dependencyValues.essentialCenter;
+ }
+
+
+ let circumradius;
+
+ if (dependencyValues.specifiedCircumradius !== null) {
+ circumradius = dependencyValues.specifiedCircumradius;
+ } else if (dependencyValues.specifiedInradius !== null) {
+ circumradius = dependencyValues.specifiedInradius / Math.cos(Math.PI / nVertices);
+ } else if (dependencyValues.specifiedSideLength !== null) {
+ circumradius = dependencyValues.specifiedSideLength / (2 * Math.sin(Math.PI / nVertices));
+ } else if (dependencyValues.specifiedPerimeter !== null) {
+ circumradius = dependencyValues.specifiedPerimeter / (2 * nVertices * Math.sin(Math.PI / nVertices))
+ } else if (dependencyValues.specifiedArea !== null) {
+ circumradius = Math.sqrt(dependencyValues.specifiedArea
+ / (nVertices / 2 * Math.sin(2 * Math.PI / nVertices)))
+ } else {
+ circumradius = dependencyValues.essentialCircumradius;
+ }
+
+ directionWithRadius = dependencyValues.essentialDirection.map(x => x * circumradius);
+
+
+ } else if (dependencyValues.haveSpecifiedCenter) {
+
+ // base polygon on center and first vertex
+
+ center = dependencyValues.specifiedCenter;
+
+ let vertex = dependencyValues.verticesAttr.stateValues.points[0].map(evaluate_to_constant);
+
+ directionWithRadius = [vertex[0] - center[0], vertex[1] - center[1]]
+
+ } else if (dependencyValues.nVerticesSpecified === 1) {
+ // one vertex, no center
+ // use vertex, direction, and a measure of size
+
+ let circumradius;
+
+ if (dependencyValues.specifiedCircumradius !== null) {
+ circumradius = dependencyValues.specifiedCircumradius;
+ } else if (dependencyValues.specifiedInradius !== null) {
+ circumradius = dependencyValues.specifiedInradius / Math.cos(Math.PI / nVertices);
+ } else if (dependencyValues.specifiedSideLength !== null) {
+ circumradius = dependencyValues.specifiedSideLength / (2 * Math.sin(Math.PI / nVertices));
+ } else if (dependencyValues.specifiedPerimeter !== null) {
+ circumradius = dependencyValues.specifiedPerimeter / (2 * nVertices * Math.sin(Math.PI / nVertices))
+ } else if (dependencyValues.specifiedArea !== null) {
+ circumradius = Math.sqrt(dependencyValues.specifiedArea
+ / (nVertices / 2 * Math.sin(2 * Math.PI / nVertices)))
+ } else {
+ circumradius = dependencyValues.essentialCircumradius;
+ }
+
+ directionWithRadius = dependencyValues.essentialDirection.map(x => x * circumradius);
+
+
+ let vertex = dependencyValues.verticesAttr.stateValues.points[0].map(evaluate_to_constant);
+
+
+ center = [vertex[0] - directionWithRadius[0], vertex[1] - directionWithRadius[1]];
+
+ } else {
+ // have at least two vertices specified, use the first 2
+ // these vertices are adjacent vertices of the polygon, in counterclockwise order
+
+ let vertex1 = dependencyValues.verticesAttr.stateValues.points[0].map(evaluate_to_constant);
+ let vertex2 = dependencyValues.verticesAttr.stateValues.points[1].map(evaluate_to_constant);
+
+ let sideVector = [vertex2[0] - vertex1[0], vertex2[1] - vertex1[1]];
+ let midpoint = [(vertex1[0] + vertex2[0]) / 2, (vertex1[1] + vertex2[1]) / 2];
+ let sideLength = Math.sqrt(sideVector[0] ** 2 + sideVector[1] ** 2);
+ let inradius = sideLength / (2 * Math.tan(Math.PI / nVertices));
+
+ let inradiusDirection = [-sideVector[1] / sideLength, sideVector[0] / sideLength];
+
+ center = [midpoint[0] + inradiusDirection[0] * inradius, midpoint[1] + inradiusDirection[1] * inradius];
+
+ directionWithRadius = [vertex1[0] - center[0], vertex1[1] - center[1]]
+
+ }
+
+ return { setValue: { centerComponents: center, directionWithRadius } }
+
+ },
+ async inverseDefinition({ desiredStateVariableValues, dependencyValues, workspace, stateValues }) {
+
+ let nVertices = dependencyValues.nVertices;
+
+ let instructions = [];
+
+
+ let desiredCenter = desiredStateVariableValues.centerComponents;
+ if (!desiredCenter) {
+ desiredCenter = workspace.desiredCenter;
+ }
+ if (!desiredCenter) {
+ desiredCenter = (await stateValues.center).map(evaluate_to_constant);
+ }
+
+ let desiredDirectionWithRadius = desiredStateVariableValues.directionWithRadius;
+ if (!desiredDirectionWithRadius) {
+ desiredDirectionWithRadius = workspace.desiredDirectionWithRadius
+ };
+ if (!desiredDirectionWithRadius) {
+ let center = (await stateValues.center).map(evaluate_to_constant);
+ let vertex1 = (await stateValues.vertices)[0].map(evaluate_to_constant);
+ desiredDirectionWithRadius = [vertex1[0] - center[0], vertex1[1] - center[1]]
+ }
+
+ workspace.desiredCenter = desiredCenter;
+ workspace.desiredDirectionWithRadius = desiredDirectionWithRadius;
+
+
+ if (dependencyValues.nVerticesSpecified === 0) {
+
+ // with no vertices, use center (specified or essential), direction, and a measure of size
+
+ if (dependencyValues.haveSpecifiedCenter) {
+ instructions.push({
+ setDependency: "specifiedCenter",
+ desiredValue: desiredCenter,
+ })
+ } else {
+ instructions.push({
+ setDependency: "essentialCenter",
+ desiredValue: desiredCenter,
+ })
+ }
+
+ let desiredCircumradius = Math.sqrt(desiredDirectionWithRadius[0] ** 2 + desiredDirectionWithRadius[1] ** 2);
+ let desiredDirection = desiredDirectionWithRadius.map(x => x / desiredCircumradius);
+
+
+ if (dependencyValues.specifiedCircumradius !== null) {
+ instructions.push({
+ setDependency: "specifiedCircumradius",
+ desiredValue: desiredCircumradius,
+ })
+ } else if (dependencyValues.specifiedInradius !== null) {
+ instructions.push({
+ setDependency: "specifiedInradius",
+ desiredValue: desiredCircumradius * Math.cos(Math.PI / nVertices),
+ })
+ } else if (dependencyValues.specifiedSideLength !== null) {
+ instructions.push({
+ setDependency: "specifiedSideLength",
+ desiredValue: desiredCircumradius * (2 * Math.sin(Math.PI / nVertices)),
+ })
+ } else if (dependencyValues.specifiedPerimeter !== null) {
+ instructions.push({
+ setDependency: "specifiedPerimeter",
+ desiredValue: desiredCircumradius * (2 * nVertices * Math.sin(Math.PI / nVertices)),
+ })
+ } else if (dependencyValues.specifiedArea !== null) {
+ instructions.push({
+ setDependency: "specifiedArea",
+ desiredValue: desiredCircumradius ** 2 * (nVertices / 2 * Math.sin(2 * Math.PI / nVertices)),
+ })
+ } else {
+ instructions.push({
+ setDependency: "essentialCircumradius",
+ desiredValue: desiredCircumradius,
+ })
+ }
+
+ instructions.push({
+ setDependency: "essentialDirection",
+ desiredValue: desiredDirection,
+ })
+
+
+ } else if (dependencyValues.haveSpecifiedCenter) {
+
+ // base polygon on center and first vertex
+
+ instructions.push({
+ setDependency: "specifiedCenter",
+ desiredValue: desiredCenter,
+ })
+
+ let desiredVertices = {
+ "0,0": me.fromAst(desiredDirectionWithRadius[0] + desiredCenter[0]),
+ "0,1": me.fromAst(desiredDirectionWithRadius[1] + desiredCenter[1])
+ };
+
+ instructions.push({
+ setDependency: "verticesAttr",
+ desiredValue: desiredVertices,
+ variableIndex: 0,
+ })
+
+ } else if (dependencyValues.nVerticesSpecified === 1) {
+ // one vertex, no center
+ // use vertex, direction, and a measure of size
+
+ let desiredCircumradius = Math.sqrt(desiredDirectionWithRadius[0] ** 2 + desiredDirectionWithRadius[1] ** 2);
+ let desiredDirection = desiredDirectionWithRadius.map(x => x / desiredCircumradius);
+
+ if (dependencyValues.specifiedCircumradius !== null) {
+ instructions.push({
+ setDependency: "specifiedCircumradius",
+ desiredValue: desiredCircumradius,
+ })
+ } else if (dependencyValues.specifiedInradius !== null) {
+ instructions.push({
+ setDependency: "specifiedInradius",
+ desiredValue: desiredCircumradius * Math.cos(Math.PI / nVertices),
+ })
+ } else if (dependencyValues.specifiedSideLength !== null) {
+ instructions.push({
+ setDependency: "specifiedSideLength",
+ desiredValue: desiredCircumradius * (2 * Math.sin(Math.PI / nVertices)),
+ })
+ } else if (dependencyValues.specifiedPerimeter !== null) {
+ instructions.push({
+ setDependency: "specifiedPerimeter",
+ desiredValue: desiredCircumradius * (2 * nVertices * Math.sin(Math.PI / nVertices)),
+ })
+ } else if (dependencyValues.specifiedArea !== null) {
+ instructions.push({
+ setDependency: "specifiedArea",
+ desiredValue: desiredCircumradius ** 2 * (nVertices / 2 * Math.sin(2 * Math.PI / nVertices)),
+ })
+ } else {
+ instructions.push({
+ setDependency: "essentialCircumradius",
+ desiredValue: desiredCircumradius,
+ })
+ }
+
+ instructions.push({
+ setDependency: "essentialDirection",
+ desiredValue: desiredDirection,
+ })
+
+ let desiredVertices = {
+ "0,0": me.fromAst(desiredDirectionWithRadius[0] + desiredCenter[0]),
+ "0,1": me.fromAst(desiredDirectionWithRadius[1] + desiredCenter[1])
+ };
+
+ instructions.push({
+ setDependency: "verticesAttr",
+ desiredValue: desiredVertices,
+ variableIndex: 0,
+ })
+
+ } else {
+ // have at least two vertices specified
+ // these vertices are adjacent vertices of the polygon, in counterclockwise order
+
+ let angle = 2 * Math.PI / nVertices;
+
+ let c = Math.cos(angle);
+ let s = Math.sin(angle);
+
+ let desiredDirectionWithRadius2 = [desiredDirectionWithRadius[0] * c - desiredDirectionWithRadius[1] * s, desiredDirectionWithRadius[0] * s + desiredDirectionWithRadius[1] * c];
+
+ let desiredVertices = {
+ "0,0": me.fromAst(desiredDirectionWithRadius[0] + desiredCenter[0]),
+ "0,1": me.fromAst(desiredDirectionWithRadius[1] + desiredCenter[1]),
+ "1,0": me.fromAst(desiredDirectionWithRadius2[0] + desiredCenter[0]),
+ "1,1": me.fromAst(desiredDirectionWithRadius2[1] + desiredCenter[1])
+ };
+
+
+ instructions.push({
+ setDependency: "verticesAttr",
+ desiredValue: desiredVertices,
+ variableIndex: 0,
+ })
+
+ }
+
+ return {
+ success: true,
+ instructions
+ }
+
+ }
+
+ }
+
+
+ stateVariableDefinitions.vertices = {
+ public: true,
+ shadowingInstructions: {
+ createComponentOfType: "math",
+ returnWrappingComponents(prefix) {
+ if (prefix === "vertexX") {
+ return [];
+ } else {
+ // vertex or entire array
+ // wrap inner dimension by both and
+ // don't wrap outer dimension (for entire array)
+ return [["point", { componentType: "mathList", isAttribute: "xs" }]];
+ }
+ },
+ },
+ isArray: true,
+ nDimensions: 2,
+ entryPrefixes: ["vertexX", "vertex"],
+ getArrayKeysFromVarName({ arrayEntryPrefix, varEnding, arraySize }) {
+ if (arrayEntryPrefix === "vertexX") {
+ // vertexX1_2 is the 2nd component of the first vertex
+ let indices = varEnding.split('_').map(x => Number(x) - 1)
+ if (indices.length === 2 && indices.every(
+ (x, i) => Number.isInteger(x) && x >= 0
+ )) {
+ if (arraySize) {
+ if (indices.every((x, i) => x < arraySize[i])) {
+ return [String(indices)];
+ } else {
+ return [];
+ }
+ } else {
+ // If not given the array size,
+ // then return the array keys assuming the array is large enough.
+ // Must do this as it is used to determine potential array entries.
+ return [String(indices)];
+ }
+ } else {
+ return [];
+ }
+ } else {
+ // vertex3 is all components of the third vertex
+
+ let pointInd = Number(varEnding) - 1;
+ if (!(Number.isInteger(pointInd) && pointInd >= 0)) {
+ return [];
+ }
+
+ if (!arraySize) {
+ // If don't have array size, we just need to determine if it is a potential entry.
+ // Return the first entry assuming array is large enough
+ return [pointInd + ",0"];
+ }
+ if (pointInd < arraySize[0]) {
+ // array of "pointInd,i", where i=0, ..., arraySize[1]-1
+ return Array.from(Array(arraySize[1]), (_, i) => pointInd + "," + i)
+ } else {
+ return [];
+ }
+ }
+
+ },
+ arrayVarNameFromPropIndex(propIndex, varName) {
+ if (varName === "vertices") {
+ if (propIndex.length === 1) {
+ return "vertex" + propIndex[0];
+ } else {
+ // if propIndex has additional entries, ignore them
+ return `vertexX${propIndex[0]}_${propIndex[1]}`
+ }
+ }
+ if (varName.slice(0, 6) === "vertex") {
+ // could be vertex or vertexX
+ let vertexNum = Number(varName.slice(6));
+ if (Number.isInteger(vertexNum) && vertexNum > 0) {
+ // if propIndex has additional entries, ignore them
+ return `vertexX${vertexNum}_${propIndex[0]}`
+ }
+ }
+ return null;
+ },
+ returnArraySizeDependencies: () => ({
+ nVertices: {
+ dependencyType: "stateVariable",
+ variableName: "nVertices"
+ }
+ }),
+ returnArraySize({ dependencyValues }) {
+ return [dependencyValues.nVertices, 2];
+ },
+ returnArrayDependenciesByKey() {
+ let globalDependencies = {
+ nVertices: {
+ dependencyType: "stateVariable",
+ variableName: "nVertices"
+ },
+ centerComponents: {
+ dependencyType: "stateVariable",
+ variableName: "centerComponents"
+ },
+ directionWithRadius: {
+ dependencyType: "stateVariable",
+ variableName: "directionWithRadius"
+ },
+
+ };
+
+ return {
+ globalDependencies
+ };
+ },
+ arrayDefinitionByKey({ globalDependencyValues }) {
+
+ // just compute all vertices every time, as they are all mutually dependent
+ // (rather than just computing for the array keys requested)
+
+ let nVertices = globalDependencyValues.nVertices;
+
+ let center = globalDependencyValues.centerComponents;
+ let directionWithRadius = globalDependencyValues.directionWithRadius;
+
+
+ let vertices = {};
+
+ if (center.some(x => !Number.isFinite(x)) || directionWithRadius.some(x => !Number.isFinite(x))) {
+ for (let vertexInd = 0; vertexInd < nVertices; vertexInd++) {
+ vertices[`${vertexInd},0`] = me.fromAst('\uff3f')
+ vertices[`${vertexInd},1`] = me.fromAst('\uff3f')
+
+ }
+ } else {
+
+ for (let vertexInd = 0; vertexInd < nVertices; vertexInd++) {
+
+ let rotation = vertexInd * 2 * Math.PI / nVertices;
+
+ let s = Math.sin(rotation);
+ let c = Math.cos(rotation);
+
+ vertices[`${vertexInd},0`] = me.fromAst(center[0] + directionWithRadius[0] * c - directionWithRadius[1] * s);
+ vertices[`${vertexInd},1`] = me.fromAst(center[1] + directionWithRadius[0] * s + directionWithRadius[1] * c);
+
+ }
+ }
+
+ return { setValue: { vertices } };
+ },
+
+ async inverseArrayDefinitionByKey({
+ desiredStateVariableValues, globalDependencyValues,
+ stateValues, workspace
+ }) {
+
+ let nVertices = globalDependencyValues.nVertices;
+
+ if (!workspace.desiredVertices) {
+ workspace.desiredVertices = {};
+ }
+ Object.assign(workspace.desiredVertices, desiredStateVariableValues.vertices);
+
+
+ let desiredKeys = Object.keys(workspace.desiredVertices);
+ let vertexInd1String = desiredKeys[0].split(",")[0];
+ let changingJustOneVertex = desiredKeys.every(v => v.split(",")[0] === vertexInd1String);
+
+ let desiredCenter;
+
+ if (changingJustOneVertex) {
+ // if change one vertex, then make sure that center stays the same
+
+ desiredCenter = (await stateValues.center).map(evaluate_to_constant);
+
+ } else {
+
+ // if change multiple vertices, then calculate center as average of all vertices
+
+ if (!workspace.allVertices) {
+ workspace.allVertices = {};
+ }
+
+ Object.assign(workspace.allVertices, workspace.desiredVertices);
+
+ let center_x = 0, center_y = 0;
+
+ for (let vertexInd = 0; vertexInd < nVertices; vertexInd++) {
+
+ let v_x = workspace.allVertices[vertexInd + ",0"];
+ if (!v_x) {
+ let vertices = await stateValues.vertices
+ v_x = vertices[vertexInd][0];
+ workspace.allVertices[vertexInd + ",0"] = v_x;
+ }
+
+
+ let v_y = workspace.allVertices[vertexInd + ",1"];
+ if (!v_y) {
+ let vertices = await stateValues.vertices
+ v_y = vertices[vertexInd][1];
+ workspace.allVertices[vertexInd + ",1"] = v_y;
+ }
+
+ center_x += evaluate_to_constant(v_x);
+ center_y += evaluate_to_constant(v_y);
+ }
+
+ center_x /= nVertices;
+ center_y /= nVertices;
+
+ desiredCenter = [center_x, center_y];
+
+ }
+
+
+ // use the first index found in desired indices to determine directionWithRadius
+ let vertexInd1 = Number(vertexInd1String);
+
+ let desiredVertex_x = workspace.desiredVertices[vertexInd1String + ",0"];
+ if (!desiredVertex_x) {
+ let vertices = await stateValues.vertices;
+ desiredVertex_x = vertices[vertexInd1][0];
+ }
+
+ let desiredVertex_y = workspace.desiredVertices[vertexInd1String + ",1"];
+ if (!desiredVertex_y) {
+ let vertices = await stateValues.vertices;
+ desiredVertex_y = vertices[vertexInd1][1];
+ }
+
+ let desiredVertex = [evaluate_to_constant(desiredVertex_x), evaluate_to_constant(desiredVertex_y)];
+
+ let centerToVertex = [desiredVertex[0] - desiredCenter[0], desiredVertex[1] - desiredCenter[1]];
+
+ let angle = -vertexInd1 * 2 * Math.PI / nVertices;
+
+ let c = Math.cos(angle);
+ let s = Math.sin(angle);
+
+ let desiredDirectionWithRadius = [centerToVertex[0] * c - centerToVertex[1] * s, centerToVertex[0] * s + centerToVertex[1] * c];
+
+
+
+ let instructions = [{
+ setDependency: "centerComponents",
+ desiredValue: desiredCenter,
+ }, {
+ setDependency: "directionWithRadius",
+ desiredValue: desiredDirectionWithRadius,
+ }];
+
+
+ return {
+ success: true,
+ instructions
+ }
+ }
+ }
+
+
+
+ stateVariableDefinitions.center = {
+ public: true,
+ isArray: true,
+ entryPrefixes: ["centerX"],
+ shadowingInstructions: {
+ createComponentOfType: "math",
+ returnWrappingComponents(prefix) {
+ if (prefix === "centerX") {
+ return [];
+ } else {
+ // entire array
+ // wrap by both and
+ return [["point", { componentType: "mathList", isAttribute: "xs" }]];
+ }
+ },
+ },
+
+ returnArraySizeDependencies: () => ({}),
+ returnArraySize: () => [2],
+
+ returnArrayDependenciesByKey() {
+
+ let globalDependencies = {
+ centerComponents: {
+ dependencyType: "stateVariable",
+ variableName: "centerComponents"
+ },
+ };
+
+ return { globalDependencies };
+ },
+
+ arrayDefinitionByKey({ globalDependencyValues }) {
+
+ return { setValue: { center: globalDependencyValues.centerComponents.map(x => me.fromAst(x)) } };
+ },
+
+ async inverseArrayDefinitionByKey({ desiredStateVariableValues, stateValues, workspace }) {
+
+
+ let desired_center_x = desiredStateVariableValues.center[0];
+ if (!desired_center_x) {
+ desired_center_x = workspace.desired_center_x;
+ }
+ if (!desired_center_x) {
+ desired_center_x = (await stateValues.center)[0]
+ }
+ workspace.desired_center_x = desired_center_x;
+
+ let desired_center_y = desiredStateVariableValues.center[1];
+ if (!desired_center_y) {
+ desired_center_y = workspace.desired_center_y;
+ }
+ if (!desired_center_y) {
+ desired_center_y = (await stateValues.center)[1]
+ }
+ workspace.desired_center_y = desired_center_y;
+
+
+
+ let instructions = [{
+ setDependency: "centerComponents",
+ desiredValue: [evaluate_to_constant(desired_center_x), evaluate_to_constant(desired_center_y)],
+ }];
+
+ return {
+ success: true,
+ instructions
+ }
+ }
+ }
+
+
+ stateVariableDefinitions.circumradius = {
+ public: true,
+ shadowingInstructions: {
+ createComponentOfType: "number",
+ },
+ returnDependencies: () => ({
+ center: {
+ dependencyType: "stateVariable",
+ variableName: "center"
+ },
+ vertex1: {
+ dependencyType: "stateVariable",
+ variableName: "vertex1"
+ }
+ }),
+ definition({ dependencyValues }) {
+ let center = dependencyValues.center.map(evaluate_to_constant);
+
+ let vertex1 = dependencyValues.vertex1.map(evaluate_to_constant);
+
+ let circumradius = Math.sqrt(
+ (vertex1[0] - center[0]) ** 2
+ + (vertex1[1] - center[1]) ** 2
+ )
+
+ return { setValue: { circumradius } };
+
+ },
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+ let center = dependencyValues.center.map(evaluate_to_constant);
+
+ let vertex1 = dependencyValues.vertex1.map(evaluate_to_constant);
+
+ let directionWithRadius = [vertex1[0] - center[0], vertex1[1] - center[1]];
+
+ let previousRadius = Math.sqrt(directionWithRadius[0] ** 2 + directionWithRadius[1] ** 2);
+
+ let desiredRadius = desiredStateVariableValues.circumradius;
+
+ let desiredDirectionWithRadius = directionWithRadius.map(x => x / previousRadius * desiredRadius);
+
+
+ let desiredVertex1 = [
+ me.fromAst(desiredDirectionWithRadius[0] + center[0]),
+ me.fromAst(desiredDirectionWithRadius[1] + center[1]),
+ ]
+
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "vertex1",
+ desiredValue: desiredVertex1
+ }]
+ }
+
+ }
+ }
+
+
+
+ stateVariableDefinitions.radius = {
+ isAlias: true,
+ targetVariableName: "circumradius"
+ };
+
+
+
+ stateVariableDefinitions.inradius = {
+ public: true,
+ shadowingInstructions: {
+ createComponentOfType: "number",
+ },
+ returnDependencies: () => ({
+ circumradius: {
+ dependencyType: "stateVariable",
+ variableName: "circumradius"
+ },
+ nVertices: {
+ dependencyType: "stateVariable",
+ variableName: "nVertices"
+ },
+ }),
+ definition({ dependencyValues }) {
+ let circumradius = dependencyValues.circumradius;
+ let nVertices = dependencyValues.nVertices;
+
+ let inradius = circumradius * Math.cos(Math.PI / nVertices)
+
+ return { setValue: { inradius } };
+
+ },
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+
+ let desiredInradius = desiredStateVariableValues.inradius;
+ let nVertices = dependencyValues.nVertices;
+
+ let desiredCircumradius = desiredInradius / Math.cos(Math.PI / nVertices);
+
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "circumradius",
+ desiredValue: desiredCircumradius
+ }]
+ }
+
+ }
+ }
+
+
+ stateVariableDefinitions.apothem = {
+ isAlias: true,
+ targetVariableName: "inradius"
+ };
+
+
+ stateVariableDefinitions.sideLength = {
+ public: true,
+ shadowingInstructions: {
+ createComponentOfType: "number",
+ },
+ returnDependencies: () => ({
+ circumradius: {
+ dependencyType: "stateVariable",
+ variableName: "circumradius"
+ },
+ nVertices: {
+ dependencyType: "stateVariable",
+ variableName: "nVertices"
+ },
+ }),
+ definition({ dependencyValues }) {
+ let circumradius = dependencyValues.circumradius;
+ let nVertices = dependencyValues.nVertices;
+
+ let sideLength = circumradius * (2 * Math.sin(Math.PI / nVertices))
+
+ return { setValue: { sideLength } };
+
+ },
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+
+ let desiredSideLength = desiredStateVariableValues.sideLength;
+ let nVertices = dependencyValues.nVertices;
+
+ let desiredCircumradius = desiredSideLength / (2 * Math.sin(Math.PI / nVertices));
+
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "circumradius",
+ desiredValue: desiredCircumradius
+ }]
+ }
+
+ }
+ }
+
+
+ stateVariableDefinitions.perimeter = {
+ public: true,
+ shadowingInstructions: {
+ createComponentOfType: "number",
+ },
+ returnDependencies: () => ({
+ circumradius: {
+ dependencyType: "stateVariable",
+ variableName: "circumradius"
+ },
+ nVertices: {
+ dependencyType: "stateVariable",
+ variableName: "nVertices"
+ },
+ }),
+ definition({ dependencyValues }) {
+ let circumradius = dependencyValues.circumradius;
+ let nVertices = dependencyValues.nVertices;
+
+ let perimeter = circumradius * (2 * nVertices * Math.sin(Math.PI / nVertices))
+
+ return { setValue: { perimeter } };
+
+ },
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+
+ let desiredPerimeter = desiredStateVariableValues.perimeter;
+ let nVertices = dependencyValues.nVertices;
+
+ let desiredCircumradius = desiredPerimeter / (2 * nVertices * Math.sin(Math.PI / nVertices));
+
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "circumradius",
+ desiredValue: desiredCircumradius
+ }]
+ }
+
+ }
+ }
+
+
+ stateVariableDefinitions.area = {
+ public: true,
+ shadowingInstructions: {
+ createComponentOfType: "number",
+ },
+ returnDependencies: () => ({
+ circumradius: {
+ dependencyType: "stateVariable",
+ variableName: "circumradius"
+ },
+ nVertices: {
+ dependencyType: "stateVariable",
+ variableName: "nVertices"
+ },
+ }),
+ definition({ dependencyValues }) {
+ let circumradius = dependencyValues.circumradius;
+ let nVertices = dependencyValues.nVertices;
+
+ let area = circumradius ** 2 * (nVertices / 2 * Math.sin(2 * Math.PI / nVertices))
+
+ return { setValue: { area } };
+
+ },
+ inverseDefinition({ desiredStateVariableValues, dependencyValues }) {
+
+ let desiredArea = desiredStateVariableValues.area;
+ let nVertices = dependencyValues.nVertices;
+
+
+
+ let desiredCircumradius = Math.sqrt(desiredArea
+ / (nVertices / 2 * Math.sin(2 * Math.PI / nVertices)))
+
+ return {
+ success: true,
+ instructions: [{
+ setDependency: "circumradius",
+ desiredValue: desiredCircumradius
+ }]
+ }
+
+ }
+ }
+
+
+
+
+ stateVariableDefinitions.nSides = {
+ isAlias: true,
+ targetVariableName: "nVertices"
+ };
+
+ return stateVariableDefinitions;
+ }
+
+
+}
+
+function evaluate_to_constant(x) {
+ let val = x.evaluate_to_constant();
+ if (val === null) {
+ return NaN;
+ } else {
+ return val;
+ }
+}
\ No newline at end of file
diff --git a/src/Parser/parser.js b/src/Parser/parser.js
index e3907bc3da..301c712b5b 100644
--- a/src/Parser/parser.js
+++ b/src/Parser/parser.js
@@ -150,7 +150,7 @@ export function parseAndCompile(inText) {
cursor.parent();
}
- let range = { selfCloseBegin: tagBegin, selfCloseEnd: cursor.to + 2 };
+ let range = { selfCloseBegin: tagBegin, selfCloseEnd: cursor.to + 3 };
// console.log(">>>toReturn", {componentType : tagName, props : attrs, children : []});