#!/usr/bin/python
"""
$Id: form_controls.py,v 1.5 2006/06/29 19:37:55 dom Exp $

The FormControlsTestCase implements the validator.testcase.TestCase
interface for the tests related to the following BP:
* Provide pre-selected default values where possible.
* Avoid free text entry where possible.
* Specify a default text entry mode, language and/or input format, if the target device is known to support it.
* Label all controls appropriately and explicitly associate labels with controls.

License
-------
Copyright (c) 2006 World Wide Web Consortium, (Massachusetts
Institute of Technology, European Research Consortium for Informatics
and Mathematics, Keio University). All Rights Reserved. This work is
distributed under the W3C Software License [1] in the hope that it
will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

[1] http://www.w3.org/Consortium/Legal/copyright-software

"""


from  validator import testcase 

# Tests regarding the AVOID_FREE_TEXT,PROVIDE_DEFAULTS,DEFAULT_INPUT_MODE,CONTROL_LABELLING BP
class FormControlsTestCase(testcase.XMLBasedTestCase):
    BpId=["AVOID_FREE_TEXT","PROVIDE_DEFAULTS","DEFAULT_INPUT_MODE","CONTROL_LABELLING"]

    def __init__(self):        
        testcase.XMLBasedTestCase.__init__(self)
        # the list of  elements that can take an access key in XHTML Basic
        self._candidates = ["input", "select", "textarea", "button"]

    def startDocument(self):
        self._hasCandidates = False
        self._hasControlsWithPossibleDefault = False
        self._inLabel = False
        self._inControl = False
        self._labels = {}
        self._labelCounter = 0
        self._radioDefault = {}
        self._inSelect = False
            
    def startElement(self, name, attrs):
        # for CONTROL_LABELLING
        if name=="label":
            self._inLabel = True
            self._labelContent = ""
            self._labelId = None
            if attrs.has_key("for"):
                self._labelId = attrs["for"]
            else:
                self._labelCounter = self._labelCounter + 1
                self._labelId = "%dcontrol" % (self._labelCounter)
            if not self._labels.has_key(self._labelId):
                self._labels[self._labelId]={}
            if attrs.has_key("title"):
                self._labels[self._labelId]["title"] = attrs["title"]
            else:
                self._labels[self._labelId]["title"] = ""
        # a form control
        elif name in self._candidates:
            self._hasCandidates = True
            self._inControl = True
            location = testcase.LineColumnLocation(self.uri,self.getEncoding(),self.locator.getLineNumber(),self.locator.getColumnNumber())
            if name=="input" or name=="textarea":
                # for AVOID_FREE_TEXT
                if not attrs.has_key("type") or attrs["type"] in ["text","password"]:
                    self.addObservation(testcase.Observation("FC6",location))
                    # for DEFAULT_INPUT_MODE
                    # @@@ this should be triggered only when the doctype supports it
                    # but we'll pretend XHTML Basic 1.1 is ready
                    if not attrs.has_key("inputmode") or not attrs["inputmode"].strip():
                        self.addObservation(testcase.Observation("FC7",location))
            # for PROVIDE_DEFAULTS
            if name=="input" and attrs.has_key("type") and attrs["type"] == "radio":
                self._hasControlsWithPossibleDefault = True
                radioId = ""
                if attrs.has_key("name"):
                    radioId = attrs["name"]
                if not self._radioDefault.has_key(radioId):
                    self._radioDefault[radioId] = {"default":False,"location":location}
                if attrs.has_key("checked"):
                    self._radioDefault[radioId]["default"] = True
            if name=="select":
                self._hasControlsWithPossibleDefault = True
                self._inSelect = location
                self._selectDefault = False
            # for CONTROL_LABELLING
            
            controlId = None
            if attrs.has_key("id"):
                controlId=attrs["id"]
            # this doesn't apply for pseudo-form controls
            if not(name=="input" and attrs.has_key("type") and attrs["type"] in ["hidden","submit","image","reset"]):
                if not self._inLabel and not controlId:
                    # if we're not inside a label, nor does the control have an id
                    # then this is an unlabeled form control
                    self.addObservation(testcase.Observation("FC1",location))
                else:
                    # if the label embeds the form control and has no given id
                    # we affect a numerical one
                    # so that we can handle them all similarly
                    if self._inLabel and not controlId:
                        controlId = self._labelId
                    if not self._labels.has_key(controlId):
                        self._labels[controlId]={}
                    self._labels[controlId]["location"]=location
        elif name=="option" and attrs.has_key("selected") and self._inSelect:
            self._selectDefault = True

    def characters(self,content):
        if self._inLabel and not self._inControl:
            self._labelContent = self._labelContent + content

    def endElement(self,name):
        if name=="label":
            self._inLabel = False
            # storing title/content
            if self._labelId:
                self._labels[self._labelId]["content"] = self._labelContent
        elif name in self._candidates:
            self._inControl = False
            # for PROVIDE_DEFAULTS
            if name=="select":
                if not self._selectDefault:
                    self.addObservation(testcase.Observation("FC10",self._inSelect))
                self._inSelect = False
            
    def endDocument(self):
        if not self._hasCandidates:
            self.addObservation(testcase.Observation("FC4",testcase.Location(self.uri)))
        else:
            # for PROVIDE_DEFAULTS
            for radioId,radioData in self._radioDefault.iteritems():
                if not radioData.has_key("default"):
                    self.addObservation(testcase.Observation("FC10",radioData["location"]))
                
            # for CONTROL_LABELLING
            for labelId,labelData in self._labels.iteritems():
                # if we are in a label with no associated control
                # we don't bother
                if not labelData.has_key("location"):
                    continue
                if not labelData.has_key("title") and not labelData.has_key("content"):
                    self.addObservation(testcase.Observation("FC1",labelData["location"]))
                # if both the title and the content of the label are empty
                # we raise an error
                elif not labelData["title"].strip() and not labelData["content"].strip():
                    self.addObservation(testcase.Observation("FC2",labelData["location"]))
                # otherwise, we store an info about the content of the label
                else:
                    if labelData["content"].strip():
                        label = labelData["content"]
                    else:
                        label = labelData["title"]
                    self.addObservation(testcase.Observation("FC3",labelData["location"],{"label":label}))
            if not "FC1" in self._observations and not "FC2" in self._observations:
                self.addObservation(testcase.Observation("FC5",testcase.Location(self.uri)))
            if not "FC6" in self._observations:
                self.addObservation(testcase.Observation("FC8",testcase.Location(self.uri)))
                self.addObservation(testcase.Observation("FC13",testcase.Location(self.uri)))
            elif not "FC7" in self._observations:
                self.addObservation(testcase.Observation("FC9",testcase.Location(self.uri)))
            if not "FC10" in self._observations:
                if self._hasControlsWithPossibleDefault:
                    self.addObservation(testcase.Observation("FC11",testcase.Location(self.uri)))
                else:
                    self.addObservation(testcase.Observation("FC12",testcase.Location(self.uri)))

# -------------------------
# Unit tests for this module
import unittest

from validator.testcase import TestResults, Observation, Location, LineColumnLocation
class Tests(unittest.TestCase):
    def _test(self,inp,res):
        a = FormControlsTestCase()
        self.assertEqual(a.run(inp),res)
        
    def testWithNoFormControls(self):
        inp = "http://dev.w3.org/cvsweb/~checkout~/2006/mwbp-validator/tests/base.xhtml"
        res = TestResults(
            [Observation("FC4",Location(inp))]
            )
        self._test(inp,res)

    def testWithLabeledFormControls(self):
        inp = "http://dev.w3.org/cvsweb/~checkout~/2006/mwbp-validator/tests/form-controls-1.xhtml"
        res = TestResults(
            [Observation("FC10",LineColumnLocation(inp,'utf-8',19,26)),
             Observation("FC3",LineColumnLocation(inp,'utf-8',18,3),{'label': u'This is the label for the first control'}),
             Observation("FC3",LineColumnLocation(inp,'utf-8',19,26),{'label': u'Second control: '}),
             Observation("FC5",Location(inp)),
             Observation("FC8",Location(inp)),
             Observation("FC13",Location(inp))
             ])
        self._test(inp,res)

    def testWithEmptyLabels(self):
        inp = "http://dev.w3.org/cvsweb/~checkout~/2006/mwbp-validator/tests/form-controls-2.xhtml"
        res = TestResults(
            [Observation("FC10",LineColumnLocation(inp,'utf-8',20,21)),
             Observation("FC2",LineColumnLocation(inp,'utf-8',19,3)),
             Observation("FC2",LineColumnLocation(inp,'utf-8',20,21)),
             Observation("FC8",Location(inp)),
             Observation("FC13",Location(inp))
             ])
        self._test(inp,res)

    def testWithUnlabeledFormControls(self):
        inp = "http://dev.w3.org/cvsweb/~checkout~/2006/mwbp-validator/tests/form-controls-3.xhtml"
        res = TestResults(
            [Observation("FC1",LineColumnLocation(inp,'utf-8',16,3)),
             Observation("FC10",LineColumnLocation(inp,'utf-8',16,3)),
             Observation("FC1",LineColumnLocation(inp,'utf-8',15,3)),
             Observation("FC8",Location(inp)),
             Observation("FC13",Location(inp))
             ])
        self._test(inp,res)

    def testWithFreeTextEntry(self):
        inp = "http://dev.w3.org/cvsweb/~checkout~/2006/mwbp-validator/tests/form-controls-4.xhtml"
        res = TestResults(
            [Observation("FC6",LineColumnLocation(inp,'utf-8',16,3)),
             Observation("FC7",LineColumnLocation(inp,'utf-8',16,3)),
             Observation("FC6",LineColumnLocation(inp,'utf-8',18,3)),
             Observation("FC3",LineColumnLocation(inp,'utf-8',16,3),{"label":"A label"}),
             Observation("FC3",LineColumnLocation(inp,'utf-8',18,3),{"label":"This one has an inputmode set"}),
             Observation("FC5",Location(inp)),
             Observation("FC12",Location(inp))])
        self._test(inp,res)

    def testWithDefaultValues(self):
        inp = "http://dev.w3.org/cvsweb/~checkout~/2006/mwbp-validator/tests/form-controls-5.xhtml"
        res = TestResults(
            [Observation("FC3",LineColumnLocation(inp,'utf-8',16,27),{'label': u'A dropdown menu: '}),
             Observation("FC3",LineColumnLocation(inp,'utf-8',15,10),{'label': u' Choix 1'}),
             Observation("FC3",LineColumnLocation(inp,'utf-8',17,10),{'label': u' Choix 2'}),
             Observation("FC5",Location(inp)),
             Observation("FC8",Location(inp)),
             Observation("FC13",Location(inp)),
             Observation("FC11",Location(inp))
             ])
        self._test(inp,res)

    def testWithoutDefaultValues(self):
        inp = "http://dev.w3.org/cvsweb/~checkout~/2006/mwbp-validator/tests/form-controls-6.xhtml"
        res = TestResults(
            [Observation("FC10",LineColumnLocation(inp,'utf-8',16,27)),
            Observation("FC3",LineColumnLocation(inp,'utf-8',16,27),{'label': u'A dropdown menu: '}),
             Observation("FC3",LineColumnLocation(inp,'utf-8',15,10),{'label': u' Choix 1'}),
             Observation("FC3",LineColumnLocation(inp,'utf-8',17,10),{'label': u' Choix 2'}),
             Observation("FC5",Location(inp)),
             Observation("FC8",Location(inp)),
             Observation("FC13",Location(inp))
            ])
        self._test(inp,res)


def _test():
    unittest.main()

if __name__ == '__main__':
    _test()



