• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dataunitylab / relational-playground / #109

10 Sep 2025 06:35PM UTC coverage: 78.51% (+0.1%) from 78.407%
#109

push

michaelmior
Fix SqlEditor test for React context refactoring

Signed-off-by: Michael Mior <mmior@mail.rit.edu>

526 of 743 branches covered (70.79%)

Branch coverage included in aggregate %.

1034 of 1244 relevant lines covered (83.12%)

14373.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.13
/src/RelExpr.js
1
// @flow
2
import React, {useEffect, useRef, useState} from 'react';
3
import {
4
  UnaryRelOp,
5
  Projection,
6
  Rename,
7
  Selection,
8
  BinaryRelOp,
9
  Except,
10
  OrderBy,
11
  Product,
12
  Join,
13
  Intersect,
14
  Union,
15
  GroupBy,
16
} from './RelOp';
17
import Relation from './Relation';
18
import {exprToString} from './util';
19
import {changeExpr} from './modules/data';
20
import ReactDOM from 'react-dom';
21
import {useReactGA} from './contexts/ReactGAContext';
22

23
import './RelExpr.css';
24

25
import type {Node, StatelessFunctionalComponent} from 'react';
26
import {useSelector} from 'react-redux';
27
import type {State} from './modules/relexp';
28

29
type Props = {
30
  changeExpr: typeof changeExpr,
31
  expr: {[string]: any},
32
  ReactGA?: any, // For backwards compatibility with tests
33
};
34

35
/** A graphical representation of a relational algebra expression */
36
const RelExpr: StatelessFunctionalComponent<Props> = (props) => {
3✔
37
  const nodeRef = useRef<?HTMLSpanElement>();
34✔
38
  const currentNode = useSelector<{data: State}, _>((state) => state.data.expr);
60✔
39
  const [isHovering, setIsHovering] = useState(false);
33✔
40
  const [isSelected, setIsSelected] = useState(false);
33✔
41
  const contextReactGA = useReactGA();
33✔
42
  const ReactGA = props.ReactGA || contextReactGA;
33✔
43

44
  useEffect(() => {
33✔
45
    // Adjust 'clicked' highlighting based on any changes to currentNode selected
46
    const clicked = props.expr === currentNode;
26✔
47
    if (!clicked) {
26!
48
      setIsSelected(false);
26✔
49
      setIsHovering(false);
26✔
50
    }
51
  }, [currentNode, props.expr]);
52

53
  /**
54
   * @param expr - a relational algebra expression object to render
55
   * @return a component representing the top-most expression
56
   */
57
  const buildExpr = (expr: {[string]: any}): Node => {
33✔
58
    // Don't try to render empty expressions
59
    if (!expr || Object.keys(expr).length === 0) {
32!
60
      return '';
×
61
    }
62
    const type = Object.keys(expr)[0];
32✔
63
    switch (type) {
32!
64
      case 'projection':
65
        return (
1✔
66
          <span>
67
            <UnaryRelOp
68
              operator={
69
                <Projection project={expr.projection.arguments.project} />
70
              }
71
            >
72
              <RelExpr
73
                expr={expr.projection.children[0]}
74
                changeExpr={props.changeExpr}
75
              />
76
            </UnaryRelOp>
77
          </span>
78
        );
79

80
      case 'selection':
81
        return (
14✔
82
          <span>
83
            <UnaryRelOp
84
              operator={
85
                <Selection
86
                  select={exprToString(expr.selection.arguments.select)}
87
                />
88
              }
89
            >
90
              <RelExpr
91
                expr={expr.selection.children[0]}
92
                changeExpr={props.changeExpr}
93
              />
94
            </UnaryRelOp>
95
          </span>
96
        );
97

98
      case 'rename':
99
        return (
1✔
100
          <span>
101
            <UnaryRelOp
102
              operator={<Rename rename={expr.rename.arguments.rename} />}
103
            >
104
              <RelExpr
105
                expr={expr.rename.children[0]}
106
                changeExpr={props.changeExpr}
107
              />
108
            </UnaryRelOp>
109
          </span>
110
        );
111

112
      case 'relation':
113
        return <Relation name={expr.relation} />;
15✔
114

115
      case 'order_by':
116
        return (
×
117
          <OrderBy
118
            columns={expr.order_by.arguments.order_by}
119
            relation={buildExpr(expr.order_by.children[0])}
120
          />
121
        );
122

123
      case 'group_by':
124
        return (
×
125
          <span>
126
            <UnaryRelOp
127
              operator={
128
                <GroupBy
129
                  groupBy={expr.group_by.arguments.groupBy}
130
                  aggregates={expr.group_by.arguments.aggregates.map(
131
                    (agg) =>
132
                      `${agg.aggregate.function}(${agg.aggregate.column})`
×
133
                  )}
134
                  selectColumns={expr.group_by.arguments.selectColumns}
135
                />
136
              }
137
            >
138
              <RelExpr
139
                expr={expr.group_by.children[0]}
140
                changeExpr={props.changeExpr}
141
              />
142
            </UnaryRelOp>
143
          </span>
144
        );
145

146
      case 'join':
147
        return (
×
148
          <Join
149
            type={expr.join.type}
150
            condition={exprToString(expr.join.condition)}
151
            left={buildExpr(expr.join.left)}
152
            right={buildExpr(expr.join.right)}
153
          />
154
        );
155

156
      case 'except':
157
      case 'intersect':
158
      case 'product':
159
      case 'union':
160
        const operator = {
×
161
          except: <Except />,
162
          intersect: <Intersect />,
163
          product: <Product />,
164
          union: <Union />,
165
        }[type];
166
        return (
×
167
          <BinaryRelOp
168
            operator={operator}
169
            left={
170
              <RelExpr expr={expr[type].left} changeExpr={props.changeExpr} />
171
            }
172
            right={
173
              <RelExpr expr={expr[type].right} changeExpr={props.changeExpr} />
174
            }
175
          />
176
        );
177

178
      default:
179
        throw new Error('Invalid expression ' + JSON.stringify(expr) + '.');
1✔
180
    }
181
  };
182

183
  /**
184
   * @param e - the event object which generated the click
185
   */
186
  const handleExprClick = (e: SyntheticMouseEvent<HTMLElement>): void => {
33✔
187
    e.stopPropagation();
1✔
188
    const node =
189
      ReactDOM.findDOMNode(nodeRef.current) instanceof HTMLElement
1!
190
        ? ReactDOM.findDOMNode(nodeRef.current)
191
        : undefined;
192

193
    const clicked = e.type === 'click' && !isSelected;
1✔
194

195
    if (props.changeExpr && node instanceof HTMLElement) {
1!
196
      if (clicked) {
1!
197
        props.changeExpr(props.expr, node);
1✔
198
      } else {
199
        props.changeExpr({}, node);
×
200
      }
201

202
      setIsHovering(false);
1✔
203
      setIsSelected(clicked);
1✔
204
    }
205

206
    if (ReactGA) {
1!
207
      ReactGA.event({
1✔
208
        category: 'User Selecting Relational Algebra Enclosure',
209
        action: Object.keys(props.expr)[0],
210
      });
211
    }
212
  };
213

214
  const handleExprHover = (e: SyntheticMouseEvent<HTMLElement>): void => {
33✔
215
    e.stopPropagation();
2✔
216
    setIsHovering(e.type === 'mouseover');
2✔
217
  };
218

219
  if (!props.expr || Object.keys(props.expr).length === 0) {
33✔
220
    return '';
1✔
221
  }
222
  const type = Object.keys(props.expr)[0];
32✔
223
  let className = 'RelExpr';
32✔
224
  if (isHovering) {
32✔
225
    className += ' hovering';
1✔
226
  }
227
  if (isSelected) {
32✔
228
    className += ' clicked';
1✔
229
  }
230
  if (type === 'relation') {
32✔
231
    return (
15✔
232
      <span className="RelExpr" style={{margin: '.4em'}}>
233
        {buildExpr(props.expr)}
234
      </span>
235
    );
236
  } else {
237
    return (
17✔
238
      <span
239
        className={className}
240
        onMouseOver={handleExprHover}
241
        onMouseOut={handleExprHover}
242
        onClick={handleExprClick}
243
        style={{margin: '.4em'}}
244
        ref={nodeRef}
245
      >
246
        {buildExpr(props.expr)}
247
      </span>
248
    );
249
  }
250
};
251

252
export default RelExpr;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc