"""
Tests for our nevow compatiblity layer.
"""

#c Copyright 2008-2024, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


from twisted.internet import defer
from twisted.web import template
from twisted.web import error as twerror
from twisted.web.template import tags as T

from gavo.formal import nevowc
from gavo.helpers import testhelpers

N_NS = "http://nevow.com/ns/nevow/0.1"
T_NS = "http://twistedmatrix.com/ns/twisted.web.template/0.1"


def loadXML(literal, namespace=N_NS):
	"""returns a stan tag from the XML literal.

	The XML literal should contain exactly one %s, which will then
	be filled with namespace.
	"""
	return nevowc.XMLString(literal%namespace).load()[0]


class TwistedLoaderTest(testhelpers.VerboseTest):
	ns = T_NS

	def testLoadSimple(self):
		res = loadXML("""
			<html xmlns:t="%s"
				><t:transparent t:render="foo" t:data="bar"/>
			</html>""", self.ns)
		iEl = res.children[0]
		self.assertEqual(iEl.tagName, '')
		self.assertEqual(iEl.render, 'foo')
		self.assertEqual(iEl.attributes["nevow:data"], "bar")

	def testLoadNested(self):
		res = loadXML("""
			<html xmlns:n="%s"
				><head xmlns:q="http://rfda"><link
					q:property="gack" href="urn:whatever"/><script
						xmlns:html="http://w3c/html"><html:p
						n:render="x">ABC</html:p></script></head><body
					><p><span n:render="bar">yadda</span></p></body>
			</html>""", self.ns)

		linkEl = res[0].children[0].children[0]
		self.assertEqual(linkEl.tagName, "link")
		self.assertEqual(linkEl.attributes["q:property"], "gack")

		scriptEl = res[0].children[0].children[1]
		self.assertEqual(scriptEl.children[0].tagName, "html:p")
		self.assertEqual(scriptEl.children[0].render, "x")

		pEl = res[0].children[1].children[0]
		self.assertEqual(pEl.children[0].render, "bar")

	def testBindNevowForbidden(self):
		self.assertRaisesWithMsg(Exception,
			"You may not bind the nevow prefix to a non-twisted or non-nevow URI",
			loadXML,
			("""<html xmlns:nevow="%s">""", "http://anything.else"))


class NevowLoaderTest(TwistedLoaderTest):
	ns = N_NS

	# run tests from parent class, just with the nevow namespace.


class LocatePatternTest(testhelpers.VerboseTest):
	def testLocateBasic(self):
		res = loadXML("""<ul
				n:render="sequence" xmlns:n="%s">
			><li class="odd" n:pattern="item"/>
			><li class="even" n:pattern="item"/>
			><li class="empty" n:pattern="empty"/></ul>""")
		baseEl = res
		pats = nevowc.locatePatterns(baseEl, ["item", "empty"])
		self.assertEqual([p.attributes["class"] for p in pats["item"]],
			["odd", "even"])
		self.assertEqual([p.attributes["class"] for p in pats["empty"]],
			["empty"])

	def testLocateRecursiveStop(self):
		res = loadXML("""<ul
				n:render="sequence" xmlns:n="%s"
			><li n:pattern="item" class="outer"
				><ul n:render="sequence"><li n:pattern="item" class="inner"
				/></ul></li></ul>""")

		pats = nevowc.locatePatterns(res, ["item"], ["sequence"])
		self.assertEqual(
			[p.attributes["class"] for p in pats["item"]],
			["outer"])

		innerUL = res.children[0].children[0]
		pats = nevowc.locatePatterns(innerUL, ["item"], ["sequence"])
		self.assertEqual(
			[p.attributes["class"] for p in pats["item"]],
			["inner"])


	def testLocateRecursiveNoStop(self):
		res = loadXML("""<ul
				n:render="sequence" xmlns:n="%s"
			><li n:pattern="item" class="outer"
				><ul n:render="sequence"><li n:pattern="item" class="inner"
				/></ul></li></ul>""")

		pats = nevowc.locatePatterns(res, ["item"], ["random"])
		self.assertEqual(
			[p.attributes["class"] for p in pats["item"]],
			["outer", "inner"])

		innerUL = res.children[0].children[0]
		pats = nevowc.locatePatterns(innerUL, ["item"], ["random"])
		self.assertEqual(
			[p.attributes["class"] for p in pats["item"]],
			['inner'])


def renderElementSync(el, renderClass=nevowc.CommonRenderers):
	"""returns a flattened string for el.

	el must not contain any deferreds, and it must not require anything
	from the request.
	"""
	res = []
	def collect(rendered):
		res.append(rendered)

	template.flattenString(None,
		nevowc.elementFromTag(el, renderClass)
	).addBoth(collect)

	if hasattr(res[0], "raiseException"):
		res[0].raiseException()
	return res[0].decode("utf-8")


class RenderSequenceTest(testhelpers.VerboseTest):
	def testBasic(self):
		el = loadXML("""<ul style="background:red"
			n:render="sequence" xmlns:n="%s"
			><li class="inner" n:pattern="item" n:render="string"/></ul>""",
			N_NS)
		el.slotData = list(range(2))
		self.assertEqual(renderElementSync(el),
			'<ul style="background:red"><li class="inner">0</li>'
			'<li class="inner">1</li></ul>')
		
	def testDetachedRender(self):
		# i.e., render functions not on the item pattern
		el = loadXML("""<ul
			n:render="sequence" xmlns:n="%s"
			><li n:pattern="item"><span class="foo" n:render="string"/></li></ul>""",
			T_NS)
		el.slotData = list(range(2))
		self.assertEqual(renderElementSync(el),
			'<ul><li><span class="foo">0</span></li><li><span class="foo">'
			'1</span></li></ul>')

	def testNestedRender(self):
		el = loadXML("""<ul
			n:render="sequence" xmlns:n="%s"
			><li n:pattern="item"><ul n:render="sequence" class="inner"
			><li n:render="string" n:pattern="item"/></ul></li></ul>""", N_NS)
		el.slotData = [list(range(2)), list(range(3,5))]

		self.assertEqual(renderElementSync(el),
			'<ul><li><ul class="inner"><li>0</li><li>1</li></ul></li><li>'
			'<ul class="inner"><li>3</li><li>4</li></ul></li></ul>')

	def testMultiPattern(self):
		el = loadXML("""<ul
			n:render="sequence" xmlns:n="%s"
			><li n:pattern="item" class="odd" n:render="string"
			/><li n:pattern="item" class="even" n:render="string"
			/><li n:pattern="separator"><hr
			/></li></ul>""", T_NS)
		el.slotData = list(range(4))
		self.assertEqual(renderElementSync(el),
			'<ul><li class="odd">0</li><li class="even">1</li><li><hr /></li>'
			'<li class="odd">2</li><li class="even">3</li></ul>')

	def testEmpty(self):
		el = loadXML("""<ul
			n:render="sequence" xmlns:n="%s"
			><li n:pattern="item">Kaputt</li>
			<p n:pattern="empty">Nix da</p></ul>""", N_NS)
		el.slotData = []
		self.assertEqual(renderElementSync(el),
			"<p>Nix da</p>")

	def testNone(self):
		el = loadXML("""<ul
			n:render="sequence" xmlns:n="%s"
			><li n:pattern="item" n:render="passthrough"></li></ul>""", N_NS)
		el.slotData = ["a", None, "b"]
		self.assertEqual(renderElementSync(el),
			"<ul><li>a</li><li>&lt;&lt;NULL&gt;&gt;</li><li>b</li></ul>")

	def testMappingSequenceTest(self):
		el = loadXML("""<ul
			n:render="sequence" xmlns:n="%s"
			><li n:pattern="item" n:render="mapping"
			><n:slot name="von"/>: <n:slot name="zu"/></li></ul>""", T_NS)
		el.slotData = [{"von": "a", "zu": "b"},{"von": "c", "zu": "d"},]
		self.assertEqual(renderElementSync(el),
			'<ul><li>a: b</li><li>c: d</li></ul>')


class DataTricksTest(testhelpers.VerboseTest):
	def testLoadData(self):
		class B(nevowc.CommonRenderers):
			def data_x(self, request, tag):
				return 4

		el = loadXML(
			'<t:invisible t:data="x" xmlns:t="%s">'
			'<p t:render="string"/>o<p t:render="string"/></t:invisible>', N_NS)

		self.assertEqual(renderElementSync(el, B), "<p>4</p>o<p>4</p>")

	def testIndexing(self):
		class B(nevowc.CommonRenderers):
			def data_x(self, request, tag):
				return ["a", "b", "c"]

			@template.renderer
			def dataAsIs(self, request, tag):
				return tag.slotData

		el = loadXML('<t:invisible t:data="x" xmlns:t="%s">'
			'<p t:render="dataAsIs" t:data="2"/>'
			'<p t:render="dataAsIs" t:data="-1"/></t:invisible>', N_NS)

		self.assertEqual(renderElementSync(el, B), "cc")

	def testComputedMethod(self):
		class B(nevowc.CommonRenderers):
			def data_gen(self, arg):
				return lambda request, tag: arg.split()

		el = loadXML("""<table xmlns:n="%s" n:render="sequence"
			n:data="gen one two"
			><tr n:pattern="item"><td n:render="string">now: </td></tr></table>""",
			T_NS)

		self.assertEqual(renderElementSync(el, B),
			'<table><tr><td>now: one</td></tr><tr><td>now: two</td></tr></table>')

	def testInvisible(self):
		class B(nevowc.CommonRenderers):
			def data_x(self, request, tag):
				return ["http://foo", "foo"]

		el = loadXML("""<a xmlns:n="%s" n:data="x"
			><n:attr name="href" n:data="0" n:render="string"
			/><n:invisible n:render="string" n:data="1"/></a>""",
			N_NS)

		self.assertEqual(renderElementSync(el, B),
			'<a href="http://foo">foo</a>')

	def testNested(self):
		class B(nevowc.CommonRenderers):
			def data_x(self, request, tag):
				return [None, "won", "too"]

			@template.renderer
			def ifdata(self, request, tag):
				if tag.slotData:
					return tag
				return ""

		el = loadXML("""<div xmlns:n="%s"
			><ul n:render="sequence" n:data="x"
				><li n:render="ifdata" n:pattern="item"
				><n:invisible n:render="string"/></li></ul></div>""",
			N_NS)

		self.assertEqual(renderElementSync(el, B),
			'<div><ul><li>won</li><li>too</li></ul></div>')

	def testPropagatingThroughRender(self):
		class B(nevowc.CommonRenderers):
			def data_x(self, request, tag):
				return ["won", "too"]

			@template.renderer
			def ifdata(self, request, tag):
				if tag.slotData:
					return tag
				return ""

		el = loadXML("""<div xmlns:n="%s" n:data="x"
			><n:invisible n:render="ifdata"
				><ul n:render="sequence"><li n:render="string" n:pattern="item"
				></li></ul></n:invisible></div>""",
			N_NS)

		self.assertEqual(renderElementSync(el, B),
			'<div><ul><li>won</li><li>too</li></ul></div>')


class RenderMappingTest(testhelpers.VerboseTest):
	def testBasic(self):
		class B(nevowc.CommonRenderers):
			def data_dict(self, request, tag):
				return [
					{"term": "foo", "desc": "An expletive"},
					{"term": "bar", "desc": "No expletive"},]

		el = loadXML(
			"""<dl xmlns:t="%s" t:render="sequence" t:data="dict"
			><t:transparent t:pattern="item" t:render="mapping"
			><dt><t:slot name="term"/></dt><dd
			><t:slot name="desc"/></dd></t:transparent></dl>""", N_NS)

		self.assertEqual(renderElementSync(el, B),
			'<dl><dt>foo</dt><dd>An expletive</dd><dt>bar</dt><dd>'
			'No expletive</dd></dl>')

	def testMappingSetsData(self):
		class B(nevowc.CommonRenderers):
			def data_dict(self, request, tag):
				return [
					{"term": "foo", "desc": "An expletive"},]

			@template.renderer
			def term(self, request, tag):
				data = tag.slotData
				return tag[data["term"]]

		el = loadXML(
			"""<dl xmlns:t="%s" t:render="sequence" t:data="dict"
			><t:transparent t:pattern="item" t:render="mapping"
			><dt t:render="term"/><dd
			><t:slot name="desc"/></dd></t:transparent></dl>""", N_NS)

		self.assertEqual(renderElementSync(el, B),
			'<dl><dt>foo</dt><dd>An expletive</dd></dl>')

	def testKey(self):
		class B(nevowc.CommonRenderers):
			def data_dict(self, request, tag):
				return {"term": "foo", "desc": "An expletive"}

			@template.renderer
			def mogrify(self, request, tag):
				data = tag.slotData
				return tag["".join(list(reversed(data)))]

		el = loadXML(
			"""<div xmlns:t="%s" t:render="mapping" t:data="dict"
			><p>Backward: <span t:data="key term" t:render="mogrify"
			/> and forward: <t:slot name="term"
			/></p></div>""", N_NS)

		self.assertEqual(renderElementSync(el, B),
			'<div><p>Backward: <span>oof</span> and forward: foo</p></div>')


class RenderTricksTest(testhelpers.VerboseTest):
	def testComputedMethod(self):
		class R(nevowc.CommonRenderers):
			@template.renderer
			def gen(self, arg):
				def fun(request, tag):
					for a in arg.split():
						yield tag.clone()[a]
				return fun
	
		el = loadXML("""<ul xmlns:n="%s"><li n:render="gen a b c"/></ul>""", T_NS)
		self.assertEqual(renderElementSync(el, R),
			'<ul><li>a</li><li>b</li><li>c</li></ul>')

	def testNonRenderRejected(self):
		el = loadXML("""<ul xmlns:n="%s" n:render="__init__"/>""", T_NS)
		# I suppose this was intended to raise UnexposedMethodError,
		# but the None default in element.lookupRenderMethod prevents
		# that right now; and then, anyway, the flattener machinery
		# dumbs it all down to a FlattnerError (which would be another
		# thing that'd be good to change.
		self.assertRaises(Exception, renderElementSync, el)

	def testRenderTwice(self):
		class R(nevowc.CommonRenderers):
			count = [1]
			def data_ct(self, request, tag):
				self.count[0] += 1
				return self.count[0]

		el = loadXML(
			"""<p xmlns:n="%s" n:data="ct"
				><span n:render="string"/></p>""", T_NS)
		self.assertEqual(renderElementSync(el, R),
			'<p><span>2</span></p>')
		self.assertEqual(renderElementSync(el, R),
			'<p><span>3</span></p>')


	def testRenderTwiceRecursive(self):
		class R(nevowc.CommonRenderers):
			count = [1]
			def data_range(self, request, tag):
				self.count[0] += 1
				return list(range(self.count[0]))

		el = loadXML(
			"""<p xmlns:n="%s" n:data="range" n:render="sequence"
				><span n:render="string" n:pattern="item"/></p>""", T_NS)
		self.assertEqual(renderElementSync(el, R),
			'<p><span>0</span><span>1</span></p>')
		self.assertEqual(renderElementSync(el, R),
			'<p><span>0</span><span>1</span><span>2</span></p>')


class FlattenSyncTest(testhelpers.VerboseTest):
	def testSimple(self):
		el = loadXML('<p xmlns:n="%s">Hello, World!</p>', T_NS)
		self.assertEqual(nevowc.flattenSync(el), b"<p>Hello, World!</p>")

	def testError(self):
		class R(template.Element):
			@template.renderer
			def fail(self, request, tag):
				raise IOError("Hello, world!")
			loader = template.TagLoader(
				T.p(render="fail")["not rendered"])

		self.assertRaises(twerror.FlattenerError,
			nevowc.flattenSync,
			R())
	
	def testDeferred(self):
		class R(template.Element):
			@template.renderer
			def fire(self, request, tag):
				d = defer.Deferred()
				return d
			loader = template.TagLoader(
				T.p(render="fire")["not rendered"])

		self.assertRaisesWithMsg(NotImplementedError,
			"flattenSync cannot deal with elements containing deferreds.",
			nevowc.flattenSync,
			(R(),))

	def testWithData(self):
		class R(nevowc.NevowcElement):
			def data_foo(self, request, tag):
				return [{"a": "foo", "b": "bar"},
					{"a": "hui", "b": "buh"}]

			loader = template.TagLoader(
				nevowc.addNevowAttributes(T.div, data="foo")[
					T.ul(render="sequence")[
						nevowc.addNevowAttributes(T.li(render="mapping"), pattern="item")[
							template.slot("a"), " x ", template.slot("b")]]])

		self.assertEqual(nevowc.flattenSync(R()),
			b'<div><ul><li>foo x bar</li><li>hui x buh</li></ul></div>')


if __name__=="__main__":
	testhelpers.main(RenderSequenceTest)
