Web Apps in TDD, Appendix, the User

Here’s the User class and its collaborators as it is right now. It is a bit more evolved than its original form : when I first wrote it, all of the logic was in the User itself as I had no need to evaluate Javascript outside of html, later I separated the two responsibilities (parsing xml/html and evaluating Javascript) since I had a need to evaluate Javascript no matter where.


public class User {
    private final JavaScript javaScript = new JavaScript();
    private final Result result = new Result();

    public User() {
        javaScript.evaluateFile("browser.js");
    }

    public User lookAt(String htmlPage) {
        JavaScriptSource source = new XomJavaScriptSource(htmlPage);
        source.evaluateWith(javaScript);
        triggerOnLoad();
        result.readOutput(javaScript);
        return this;
    }

    public String currentSight() {
        return result.nextValue();
    }

    private void triggerOnLoad() {
        javaScript.evaluateScript("window.onload();", "onload");
    }

}



And here’s “JavaScript”, which manages everything Rhino-related.


public class JavaScript {
    private final Context context;
    private final ScriptableObject scope;

    public JavaScript() {
        context = Context.enter();
        scope = context.initStandardObjects();
    }

    public Object valueOf(String variableName) {
        return scope.get(variableName);
    }

    public void evaluateScript(String script, String scriptName) {
        context.evaluateString(scope, script, scriptName, 1, null);
    }

    public void evaluateScript(String script) {
        evaluateScript(script, "script");
    }

    public void evaluateFile(String sourceFileName) {
        try {
            context.evaluateReader(scope, read(sourceFileName), sourceFileName, 1, null);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private InputStreamReader read(String sourceFileName) {
        return new InputStreamReader(getClass().getClassLoader().getResourceAsStream(sourceFileName));
    }
}




This is “Result”, which extracts values from the results array in Javascript.


public class Result {
    private NativeArray output = new NativeArray(0);
    private int current = 0;

    public void readOutput(JavaScript javaScript) {
        output = (NativeArray) javaScript.valueOf("output");
    }

    public String nextValue() {
        return (String) output.get(current++);
    }
}



Finally, this is the class that hides the fact that scripts are mixed within html.


public class XomJavaScriptSource implements JavaScriptSource {

    private final Document document;

    public XomJavaScriptSource(String htmlPage) {
        document = parsePage(htmlPage);
    }

    @Override
    public void evaluateWith(JavaScript javaScript) {
        Nodes scriptNodes = document.query("//script");
        for (int i = 0; i < scriptNodes.size(); i++) {
            evaluateNode(scriptNodes.get(i), javaScript);
        }
    }

    private final Document parsePage(String htmlPage) {
        try {
            return new Builder().build(htmlPage, null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void evaluateNode(Node scriptNode, JavaScript javaScript) {
        if (scriptNode instanceof Element) {
            Attribute sourceAttribute = ((Element) scriptNode).getAttribute("src");
            if (sourceAttribute != null) {
                javaScript.evaluateFile(sourceAttribute.getValue());
                return;
            }
        }
        javaScript.evaluateScript(scriptNode.getValue());
    }
}
Advertisements