11ssList = { } ;
22
3- class SpreadSheet {
3+ class SpreadSheet extends RunestoneBase {
44 constructor ( opts ) {
5+ super ( SpreadSheet ) ;
56 let orig = opts . orig ;
67 this . div_id = orig . id ;
78 this . sheet_id = `${ this . div_id } _sheet` ;
89 this . data = eval ( `${ this . div_id } _data` ) ;
10+ this . autograde = $ ( orig ) . data ( "autograde" ) ;
11+ this . suffix = window [ `${ this . div_id } _asserts` ] ;
12+ this . renderSheet ( ) ;
913
10- this . renderSheet ( )
14+ if ( this . autograde ) {
15+ this . addAutoGradeButton ( ) ;
16+ this . addOutput ( ) ;
17+ }
1118 }
1219
1320 renderSheet ( ) {
14- let div = document . getElementById ( this . sheet_id )
15- this . table = jexcel ( div , { data :this . data } )
21+ let div = document . getElementById ( this . sheet_id ) ;
22+ this . table = jexcel ( div , { data :this . data } ) ;
1623 }
24+
25+ addAutoGradeButton ( ) {
26+ let div = document . getElementById ( this . div_id ) ;
27+ var butt = document . createElement ( "button" ) ;
28+ $ ( butt ) . text ( "Check" ) ;
29+ $ ( butt ) . addClass ( "btn btn-success run-button" ) ;
30+ div . appendChild ( butt ) ;
31+ this . gradeButton = butt ;
32+ $ ( butt ) . click ( this . doAutoGrade . bind ( this ) ) ;
33+ $ ( butt ) . attr ( "type" , "button" ) ;
34+ }
35+
36+ addOutput ( ) {
37+ this . output = document . createElement ( 'pre' ) ;
38+ this . output . id = this . divid + '_stdout' ;
39+ $ ( this . output ) . css ( "visibility" , "hidden" ) ;
40+ let div = document . getElementById ( this . div_id ) ;
41+ div . appendChild ( this . output ) ;
42+ }
43+
44+ doAutoGrade ( ) {
45+ let tests = this . suffix ;
46+ this . passed = 0 ;
47+ this . failed = 0 ;
48+ // Tests should be of the form
49+ // assert row,col oper value for example
50+ // assert 4,4 == 3
51+ let result = "" ;
52+ tests = tests . filter ( function ( s ) {
53+ return s . indexOf ( 'assert' ) > - 1 ;
54+ } ) ;
55+ for ( let test of tests ) {
56+ let assert , loc , oper , expected ;
57+ [ assert , loc , oper , expected ] = test . split ( / \s + / ) ;
58+ result += this . testOneAssert ( loc , oper , expected ) ;
59+ result += "\n" ;
60+ }
61+ let pct = 100 * this . passed / ( this . passed + this . failed ) ;
62+ pct = pct . toLocaleString ( undefined , { maximumFractionDigits : 2 } ) ;
63+ result += `You passed ${ this . passed } out of ${ this . passed + this . failed } tests for ${ pct } %` ;
64+ this . logBookEvent ( { event : 'unittest' ,
65+ div_id : this . divid ,
66+ course : eBookConfig . course ,
67+ act : `percent:${ pct } :passed:${ this . passed } :failed:${ this . failed } `
68+ } ) ;
69+ $ ( this . output ) . css ( "visibility" , "visible" ) ;
70+ $ ( this . output ) . text ( result ) ;
71+ }
72+
73+ testOneAssert ( cell , oper , expected ) {
74+ let actual = this . getCellDisplayValue ( cell ) ;
75+ const operators = {
76+ "==" : function ( operand1 , operand2 ) {
77+ return operand1 == operand2 ;
78+ } ,
79+ "!=" : function ( operand1 , operand2 ) {
80+ return operand1 != operand2 ;
81+ } ,
82+ ">" : function ( operand1 , operand2 ) {
83+ return operand1 > operand2 ;
84+ } ,
85+ "<" : function ( operand1 , operand2 ) {
86+ return operand1 > operand2 ;
87+ }
88+ } ;
89+
90+ let res = operators [ oper ] ( actual , expected ) ;
91+ let output = "" ;
92+ if ( res ) {
93+ output = `Pass: ${ actual } ${ oper } ${ expected } in ${ cell } ` ;
94+ this . passed ++ ;
95+ } else {
96+ output = `Failed ${ actual } ${ oper } ${ expected } in cell ${ cell } ` ;
97+ this . failed ++ ;
98+ }
99+ return output ;
100+ }
101+
102+
103+
104+ // If the cell contains a formula, this call will return the formula not the computed value
105+ getCellSource ( cell ) {
106+ return this . table . getValue ( cell ) ;
107+ }
108+
109+ // If the cell contains a formula this call will return the computed value
110+ getCellDisplayValue ( cell ) {
111+ let parts = cell . match ( / \$ ? ( [ A - Z ] + ) \$ ? ( [ 0 - 9 ] + ) / ) ;
112+ let x = this . columnToIndex ( parts [ 1 ] ) ;
113+ let y = parts [ 2 ] - 1 ;
114+ let res = this . table . el . querySelector ( `[data-x="${ x } "][data-y="${ y } "]` ) ;
115+ return res . innerText ;
116+ }
117+
118+ columnToIndex ( colName ) {
119+ // Convert the column name to a number A = 0 AA = 26 BA = 52, etc
120+ let base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ;
121+ let result = 0 ;
122+
123+ for ( let i = 0 , j = colName . length - 1 ; i < colName . length ; i += 1 , j -= 1 ) {
124+ result += Math . pow ( base . length , j ) * ( base . indexOf ( colName [ i ] ) + 1 ) ;
125+ }
126+
127+ return result - 1 ;
128+ }
129+
17130}
18131
19132$ ( document ) . bind ( "runestone:login-complete" , function ( ) {
@@ -24,6 +137,6 @@ $(document).bind("runestone:login-complete", function () {
24137} ) ;
25138
26139if ( typeof component_factory === 'undefined' ) {
27- component_factory = { }
140+ component_factory = { } ;
28141}
29- component_factory [ ' spreadsheet' ] = function ( opts ) { return new SpreadSheet ( opts ) }
142+ component_factory . spreadsheet = function ( opts ) { return new SpreadSheet ( opts ) ; } ;
0 commit comments